以前写的全局钩子都是用unmanaged C或C++写个DLL来实现,可大家都知道,C#是基于.Net Framework的,是managed,怎么实现全局钩子呢?于是开始到网上搜索,好不容易找到一篇,
318804 - HOW TO: Set a Windows Hook in Visual C# .NET,里面详细的说明了如何使用鼠标钩子捕获鼠标的移动等,可是,它只能在Application里起作用,出了Application就没用了,就是说它还是没有实现全局钩子,而且文章结尾处说:“
Global Hooks are not supported in the .NET Framework...”,这可怎么办呢?
别担心,办法总是有的,经过一番摸索以后,发现WH_KEYBORAD_LL和WH_MOUSE_LL这两个low-level的hook可以被安装成全局的,这就好办了,我们不妨用这两个low-level的hook替换掉WH_KEYBORAD和WH_MOUSE,于是开始测试。结果成功了,在C#里实现了全局钩子。
我们来看一下主要代码段。
首先倒入所需要的windows函数,主要有三个,SetWindowsHookEX用来安装钩子,UnhookWindowsHookEX用来卸载钩子以及CallNextHookEX用来将hook信息传递到链表中下一个hook处理过程。
[DllImport(
"
user32.dll
"
, CharSet
=
CharSet.Auto,
CallingConvention
=
CallingConvention.StdCall, SetLastError
=
true
)]
private
static
extern
int
SetWindowsHookEx(
int
idHook,
HookProc lpfn,
IntPtr hMod,
int
dwThreadId);
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
[DllImport(
"
user32.dll
"
, CharSet
=
CharSet.Auto,
CallingConvention
=
CallingConvention.StdCall, SetLastError
=
true
)]
private
static
extern
int
UnhookWindowsHookEx(
int
idHook);
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
[DllImport(
"
user32.dll
"
, CharSet
=
CharSet.Auto,
CallingConvention
=
CallingConvention.StdCall)]
private
static
extern
int
CallNextHookEx(
int
idHook,
int
nCode,
int
wParam,
IntPtr lParam);
下面是有关这两个low-level hook在Winuser.h中的定义:
/**/
/// <summary>
/// Windows NT/2000/XP: Installs a hook procedure that monitors low-level mouse input events.
/// </summary>
private
const
int
WH_MOUSE_LL
=
14
;
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
/**/
/// <summary>
/// Windows NT/2000/XP: Installs a hook procedure that monitors low-level keyboard input events.
/// </summary>
private
const
int
WH_KEYBOARD_LL
=
13
;
在安装全局钩子的时候,我们就要做替换了,将WH_MOUSE和WH_KEYBORAD分别换成WH_MOUSE_LL和WH_KEYBORAD_LL:
//
install hook
hMouseHook
=
SetWindowsHookEx(
WH_MOUSE_LL,
//
原来是WH_MOUSE
MouseHookProcedure,
Marshal.GetHINSTANCE(
Assembly.GetExecutingAssembly().GetModules()[
0
]),
0
);
![](https://i-blog.csdnimg.cn/blog_migrate/f0cd6c7f9e7ae96feae062cb48f670f0.gif)
//
install hook
hKeyboardHook
=
SetWindowsHookEx(
WH_KEYBOARD_LL,
//
原来是WH_KEYBORAD
KeyboardHookProcedure,
Marshal.GetHINSTANCE(
Assembly.GetExecutingAssembly().GetModules()[
0
]),
0
);
这样替换了之后,我们就可以实现全局钩子了,而且,不需要写DLL。看一下程序运行情况:
下面是关于鼠标和键盘的两个Callback函数:
private
int
MouseHookProc(
int
nCode,
int
wParam, IntPtr lParam)
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
// if ok and someone listens to our events
if ((nCode >= 0) && (OnMouseActivity != null))
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
//Marshall the data from callback.
MouseLLHookStruct mouseHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
//detect button clicked
MouseButtons button = MouseButtons.None;
short mouseDelta = 0;
switch (wParam)
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
case WM_LBUTTONDOWN:
//case WM_LBUTTONUP:
//case WM_LBUTTONDBLCLK:
button = MouseButtons.Left;
break;
case WM_RBUTTONDOWN:
//case WM_RBUTTONUP:
//case WM_RBUTTONDBLCLK:
button = MouseButtons.Right;
break;
case WM_MOUSEWHEEL:
//If the message is WM_MOUSEWHEEL, the high-order word of mouseData member is the wheel delta.
//One wheel click is defined as WHEEL_DELTA, which is 120.
//(value >> 16) & 0xffff; retrieves the high-order word from the given 32-bit value
mouseDelta = (short)((mouseHookStruct.mouseData >> 16) & 0xffff);
//TODO: X BUTTONS (I havent them so was unable to test)
//If the message is WM_XBUTTONDOWN, WM_XBUTTONUP, WM_XBUTTONDBLCLK, WM_NCXBUTTONDOWN, WM_NCXBUTTONUP,
//or WM_NCXBUTTONDBLCLK, the high-order word specifies which X button was pressed or released,
//and the low-order word is reserved. This value can be one or more of the following values.
//Otherwise, mouseData is not used.
break;
}
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
//double clicks
int clickCount = 0;
if (button != MouseButtons.None)
if (wParam == WM_LBUTTONDBLCLK || wParam == WM_RBUTTONDBLCLK) clickCount = 2;
else clickCount = 1;
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
//generate event
MouseEventArgs e = new MouseEventArgs(
button,
clickCount,
mouseHookStruct.pt.x,
mouseHookStruct.pt.y,
mouseDelta);
//raise it
OnMouseActivity(this, e);
}
//call next hook
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
private
int
KeyboardHookProc(
int
nCode, Int32 wParam, IntPtr lParam)
![](https://i-blog.csdnimg.cn/blog_migrate/34031c708bfe702fe82d01ff5c6593aa.gif)
{
//indicates if any of underlaing events set e.Handled flag
bool handled = false;
//it was ok and someone listens to events
if ((nCode >= 0) && (KeyDown != null || KeyUp != null || KeyPress != null))
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
//read structure KeyboardHookStruct at lParam
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
//raise KeyDown
if (KeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
KeyDown(this, e);
handled = handled || e.Handled;
}
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
// raise KeyPress
if (KeyPress != null && wParam == WM_KEYDOWN)
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
bool isDownShift = ((GetKeyState(VK_SHIFT) & 0x80) == 0x80 ? true : false);
bool isDownCapslock = (GetKeyState(VK_CAPITAL) != 0 ? true : false);
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
byte[] keyState = new byte[256];
GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (ToAscii(MyKeyboardHookStruct.vkCode,
MyKeyboardHookStruct.scanCode,
keyState,
inBuffer,
MyKeyboardHookStruct.flags) == 1)
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
char key = (char)inBuffer[0];
if ((isDownCapslock ^ isDownShift) && Char.IsLetter(key)) key = Char.ToUpper(key);
KeyPressEventArgs e = new KeyPressEventArgs(key);
KeyPress(this, e);
handled = handled || e.Handled;
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
// raise KeyUp
if (KeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
![](https://i-blog.csdnimg.cn/blog_migrate/3112b7b6526db5bc83e275260ae60525.gif)
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
KeyUp(this, e);
handled = handled || e.Handled;
}
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
}
![](https://i-blog.csdnimg.cn/blog_migrate/587e34b10dcf5efbc0859b53470a2db3.gif)
//if event handled in application do not handoff to other listeners
if (handled)
return 1;
else
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}