看看C# Hook 如何實現
目前的程序中想要添加Hook,截獲鍵盤按鍵消息,所以上網找了一下關于C# Hook的東西。發現很多人都在說在添加C# Hook不成功和不穩定,而建議使用C++封一個Dll給C#使用。可喜的是最后我還是成功的在程序中使用C#添加了Hook,經過測試還是沒有什么問題的。
進行Hook需要使用三個API函數
SetWindowsHookEx 進行Hook的注冊
UnhookWindowsHookEx 取消已經注冊的Hook
CallNextHookEx 將消息傳遞給下一個Hook函數
看一下定義
- [DllImport("user32.dll")]
- private static extern IntPtr SetWindowsHookEx(
- HookType code, HookProc func, IntPtr instance, int threadID);
- [DllImport("user32.dll")]
- private static extern int UnhookWindowsHookEx(IntPtr hook);
- [DllImport("user32.dll")]
- private static extern int CallNextHookEx(
- IntPtr hook, int code, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
我們需要定義一個delegate來接收消息
- private delegate int HookProc(int code, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
定義兩個event提供給外界使用
- public delegate void HookEventHandler(object sender, HookEventArgs e);
- public event HookEventHandler KeyDown;
- public event HookEventHandler KeyUp;
因為要接收的是鍵盤的消息所以要定義一些消息,和我們要接收的結構。
- public class HookEventArgs : EventArgs
- {
- // using Windows.Forms.Keys instead of Input.Key since the Forms.Keys maps
- // to the Win32 KBDLLHOOKSTRUCT virtual key member, where Input.Key does not
- public Keys Key;
- public bool Alt;
- public bool Control;
- public bool Shift;
- public HookEventArgs(UInt32 keyCode)
- {
- // detect what modifier keys are pressed, using
- // Windows.Forms.Control.ModifierKeys instead of Keyboard.Modifiers
- // since Keyboard.Modifiers does not correctly get the state of the
- // modifier keys when the application does not have focus
- this.Key = (Keys)keyCode;
- this.Alt = (System.Windows.Forms.Control.ModifierKeys & Keys.Alt) != 0;
- this.Control = (System.Windows.Forms.Control.ModifierKeys & Keys.Control) != 0;
- this.Shift = (System.Windows.Forms.Control.ModifierKeys & Keys.Shift) != 0;
- }
- }
- private enum HookType : int
- {
- WH_JOURNALRECORD = 0,
- WH_JOURNALPLAYBACK = 1,
- WH_KEYBOARD = 2,
- WH_GETMESSAGE = 3,
- WH_CALLWNDPROC = 4,
- WH_CBT = 5,
- WH_SYSMSGFILTER = 6,
- WH_MOUSE = 7,
- WH_HARDWARE = 8,
- WH_DEBUG = 9,
- WH_SHELL = 10,
- WH_FOREGROUNDIDLE = 11,
- WH_CALLWNDPROCRET = 12,
- WH_KEYBOARD_LL = 13,
- WH_MOUSE_LL = 14
- }
- public struct KBDLLHOOKSTRUCT
- {
- public UInt32 vkCode;
- public UInt32 scanCode;
- public UInt32 flags;
- public UInt32 time;
- public IntPtr extraInfo;
- }
關鍵的在這里注冊C# Hook
- private void Install()
- {
- if (_hookHandle != IntPtr.Zero)
- return;
- Module[] list = System.Reflection.Assembly.GetExecutingAssembly().GetModules();
- System.Diagnostics.Debug.Assert(list != null && list.Length > 0);
- _hookHandle = SetWindowsHookEx(_hookType,
- _hookFunction, Marshal.GetHINSTANCE(list[0]), 0);
- }
哦,還有HookType _hookType = HookType.WH_KEYBOARD_LL; 因為要截獲鍵盤消息
還有_hookFunction = new HookProc(HookCallback);
其實最關鍵的是Marshal.GetHINSTANCE(list[0])得到當前程序的instance,這樣這個C# Hook就是全局的C# Hook,這個位置也可以是null,這樣就不是全局C# Hook。
有個很奇怪的現象就是這個函數,在調試狀態執行不能成功,而做成Release以后運行成功,所以你在做程序時,不要因為調試失敗而對這個函數有懷疑,編一個Release版本的程序,獨立運行再試一下。我使用的環境是VS2005 + Vista。
接收消息的函數
- private int HookCallback(int code, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam)
- {
- if (code < 0)
- return CallNextHookEx(_hookHandle, code, wParam, ref lParam);
- if ((lParam.flags & 0x80) != 0 && this.KeyUp != null)
- this.KeyUp(this, new HookEventArgs(lParam.vkCode));
- if ((lParam.flags & 0x80) == 0 && this.KeyDown != null)
- {
- this.KeyDown(this, new HookEventArgs(lParam.vkCode));
- if (lParam.vkCode == 44)
- {
- return 1;
- }
- }
- return CallNextHookEx(_hookHandle, code, wParam, ref lParam);
- }
這里會區分KeyUp和KeyDown,注意一定要調用CallNextHookEx,這樣會將這個消息在系統中繼續傳遞,這很重要。除非你想阻止這個消息,也不希望其他程序再處理這個消息。
下面最后的操作,釋放注冊。
- private void Uninstall()
- {
- if (_hookHandle != IntPtr.Zero)
- {
- UnhookWindowsHookEx(_hookHandle);
- _hookHandle = IntPtr.Zero;
- }
- }
Ok,完成了。
【編輯推薦】