今天,有個同事問我,怎樣在C#中使用全局鉤子?以前寫的全局鉤子都是用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處理過程。
C#代碼
- [DllImport("user32.dll", CharSet = CharSet.Auto,
- CallingConvention = CallingConvention.StdCall, SetLastError = true)]
- private static extern int SetWindowsHookEx(
- int idHook,
- HookProc lpfn,
- IntPtr hMod,
- int dwThreadId);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto,
- CallingConvention = CallingConvention.StdCall, SetLastError = true)]
- private static extern int UnhookWindowsHookEx(int idHook);
-
- [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;
- /// <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);
-
- //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)
-
- {
-
- // if ok and someone listens to our events
-
- if ((nCode >= 0) && (OnMouseActivity != null))
-
- {
-
- //Marshall the data from callback.
-
- MouseLLHookStruct mouseHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
-
-
- //detect button clicked
-
- MouseButtons button = MouseButtons.None;
-
- short mouseDelta = 0;
-
- switch (wParam)
-
- {
-
- 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;
-
- }
-
-
- //double clicks
-
- int clickCount = 0;
-
- if (button != MouseButtons.None)
-
- if (wParam == WM_LBUTTONDBLCLK || wParam == WM_RBUTTONDBLCLK) clickCount = 2;
-
- else clickCount = 1;
-
-
- //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)
- {
- //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))
- {
- //read structure KeyboardHookStruct at lParam
- KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
- //raise KeyDown
- if (KeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
- {
- Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
- KeyEventArgs e = new KeyEventArgs(keyData);
- KeyDown(this, e);
- handled = handled || e.Handled;
- }
-
- // raise KeyPress
- if (KeyPress != null && wParam == WM_KEYDOWN)
- {
- bool isDownShift = ((GetKeyState(VK_SHIFT) & 0x80) == 0x80 ? true : false);
- bool isDownCapslock = (GetKeyState(VK_CAPITAL) != 0 ? true : false);
-
- byte[] keyState = new byte[256];
- GetKeyboardState(keyState);
- byte[] inBuffer = new byte[2];
- if (ToAscii(MyKeyboardHookStruct.vkCode,
- MyKeyboardHookStruct.scanCode,
- keyState,
- inBuffer,
- MyKeyboardHookStruct.flags) == 1)
- {
- 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;
- }
- }
-
- // raise KeyUp
- if (KeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
- {
- Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
- KeyEventArgs e = new KeyEventArgs(keyData);
- KeyUp(this, e);
- handled = handled || e.Handled;
- }
-
- }
-
- //if event handled in application do not handoff to other listeners
- if (handled)
- return 1;
- else
- return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
- }