前言:ide
由於項目中須要使用到快捷鍵,因此上網找資料瞭解關於快捷鍵的實現技術,因而有了鍵盤鉤子的使用學習。在網上了解到,鍵盤鉤子其實只是不少種鉤子中的其中一種。所謂鉤子:請看下面關於鉤子的描述(來自百度百科):函數
Windows系統是創建在事件驅動的機制上的,說穿了就是整個系統都是經過消息的傳遞來實現的。而鉤子是Windows系統中很是重要的系統接口,用它能夠截獲並處理送給其餘應用程序的消息,來完成普通應用程序難以實現的功能。性能
鉤子能夠監視系統或進程中的各類事件消息,截獲發往目標窗口的消息並進行處理。這樣,咱們就能夠在系統中安裝自定義的鉤子,監視系統中特定事件的發生,完成特定的功能,好比截獲鍵盤、鼠標的輸入,屏幕取詞,日誌監視等等。學習
鉤子的本質是一段用以處理系統消息的程序,經過系統調用,將其掛入系統。鉤子的種類有不少,每種鉤子能夠截獲並處理相應的消息,每當特定的消息發出,在到達目的窗口以前,鉤子程序先行截獲該消息、獲得對此消息的控制權。此時在鉤子函數中就能夠對截獲的消息進行加工處理,甚至能夠強制結束消息的傳遞。this
本文咱們主要來談談全局鉤子和進程鉤子的使用。spa
全局鉤子:全局鉤子,可以截獲全部運行在操做系統上的程序發送的消息,可是因其全局性,鉤子安裝以後,會比較損耗性能,在使用完畢以後,必須實時的卸載。操作系統
進程鉤子:能夠針對某一個進程,僅僅截獲某一個應用程序的消息,比較具備針對性,適用於普通的信息管理系統。線程
鉤子程序是封裝在User32.dll中的方法,若是咱們的程序須要用到鉤子,首先須要將鉤子對應的程序集導入到咱們的系統中。代碼以下:日誌
/// <summary> /// 獲取窗體線程ID /// </summary> /// <param name="hwnd">窗體句柄</param> /// <param name="ID"></param> /// <returns></returns> [DllImport("User32.dll", CharSet = CharSet.Auto)] public static extern int GetWindowThreadProcessId(IntPtr hwnd, int ID); /// <summary> /// 設置鉤子 /// </summary> /// <param name="idHook">鉤子id</param> /// <param name="lpfn">鉤子處理方法</param> /// <param name="hInstance">句柄</param> /// <param name="threadId">線程id</param> /// <returns></returns> [DllImport("user32.dll")] public static extern int SetWindowsHookEx(int idHook, HookHandle lpfn, IntPtr hInstance, int threadId); /// <summary> /// 取消鉤子 /// </summary> /// <param name="idHook"></param> /// <returns></returns> [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern bool UnhookWindowsHookEx(int idHook); /// <summary> /// 調用下一個鉤子 /// </summary> /// <param name="idHook">本鉤子id</param> /// <param name="nCode"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <returns></returns> [DllImport("user32.dll")] public static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam); /// <summary> /// 獲取當前線程ID /// </summary> /// <returns></returns> [DllImport("kernel32.dll")] public static extern int GetCurrentThreadId(); [DllImport("kernel32.dll")] public static extern IntPtr GetModuleHandle(string name);
在系統使用中,咱們對鉤子進行了簡單的封裝,針對全局鉤子和進程鉤子共有的特性,抽象出鉤子基類code
/// <summary> /// 鍵盤鉤子 /// </summary> public abstract class KeyBoardHook { #region 字段 /// <summary> /// 當前鉤子的id /// </summary> protected int hHookId = 0; /// <summary> /// 外部調用的鍵盤處理事件 /// </summary> protected ProcessKeyHandle clientMethod = null; /// <summary> /// 當前模塊的句柄 /// </summary> protected IntPtr hookWindowPtr = IntPtr.Zero; /// <summary> /// 勾子程序處理事件 /// </summary> protected HookHandle keyBoardHookProcedure; protected int hookKey; #endregion #region 屬性 /// <summary> /// 獲取或設置鉤子的惟一標誌 /// </summary> public int HookKey { get { return this.hookKey; } set { this.hookKey = value; } } #endregion /// <summary> /// 安裝鉤子 /// </summary> /// <param name="clientMethod"></param> /// <returns></returns> public abstract bool Install(ProcessKeyHandle clientMethod); /// <summary> /// 鉤子處理函數 /// </summary> /// <param name="nCode"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <returns></returns> protected abstract int GetHookProc(int nCode, int wParam, IntPtr lParam); /// <summary> /// 卸載鉤子 /// </summary> public virtual void UnInstall() { bool retKeyboard = true; if (hHookId != 0) { retKeyboard = Win32API.UnhookWindowsHookEx(hHookId); hHookId = 0; } //if (hookWindowPtr != IntPtr.Zero) //{ // Marshal.FreeHGlobal(hookWindowPtr); //} if (!retKeyboard) { throw new Exception("UnhookWindowsHookEx failed."); } } }
/// <summary> /// 全局鉤子,慎用,獲取全部進程的按鍵信息,耗費系統資源 /// </summary> internal class GlobalHook : KeyBoardHook { public override bool Install(ProcessKeyHandle clientMethod) { try { //客戶端傳入的委託,即截獲消息以後,對消息的過濾和處理的方法 this.clientMethod = clientMethod; // 安裝鍵盤鉤子 if (hHookId == 0) { keyBoardHookProcedure = GetHookProc; hookWindowPtr = Win32API.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName); hHookId = Win32API.SetWindowsHookEx( (int)HookType.WH_KEYBOARD_LL,//調用系統方法安裝鉤子,第一個參數標識鉤子的類型13爲全局鉤子 keyBoardHookProcedure, hookWindowPtr, 0); //若是設置鉤子失敗. if (hHookId == 0) UnInstall(); } return true; } catch { return false; } } protected override int GetHookProc(int nCode, int wParam, IntPtr lParam) { ////參數 nCode 的可選值: //HC_ACTION = 0; {} //HC_GETNEXT = 1; {} //HC_SKIP = 2; {} //HC_NOREMOVE = 3; {} //HC_NOREM = HC_NOREMOVE; {} //HC_SYSMODALON = 4; {} //HC_SYSMODALOFF = 5; {} if (nCode >= 0 && nCode != 3) { //wParam = = 0x101 // 鍵盤擡起 // 鍵盤按下 if (wParam == 0x100) { //觸發事件把安裝信息通知客戶端 if (clientMethod != null) { KeyBoardHookStruct kbh = (KeyBoardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyBoardHookStruct)); Keys key = (Keys)kbh.VkCode; bool handle; clientMethod(this.hookKey, key, out handle); if (handle)//若是處理了就直接中止 1: { Win32API.CallNextHookEx(hHookId, nCode, wParam, lParam); return 1; } } } } return Win32API.CallNextHookEx(hHookId, nCode, wParam, lParam); } } /// <summary> /// 鍵盤鉤子的類型 /// </summary> public enum KeyBoardHookType { Global = 2,//全局鉤子 Process = 13//進程鉤子 } /// <summary> /// 鍵盤處理事件委託,接受返回win32的委託 /// </summary> /// <param name="nCode"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <returns></returns> public delegate int HookHandle(int nCode, int wParam, IntPtr lParam); /// <summary> /// 客戶端處理鉤子回調的鍵盤處理事件 /// </summary> /// <param name="hookKey">鉤子的惟一標誌</param> /// <param name="key">按下的鍵</param> /// <param name="handle">客戶端是否處理了這個值</param> public delegate void ProcessKeyHandle(int hookKey, Keys key, out bool handle); internal enum HookType { WH_KEYBOARD = 2,//私有鉤子 WH_KEYBOARD_LL = 13//全局鉤子 } /// <summary> /// 全局鉤子時,轉化的結構體 /// </summary> [StructLayout(LayoutKind.Sequential)] internal class KeyBoardHookStruct { /// <summary> /// 表達一個在1到254間的虛擬鍵盤碼 /// </summary> public int VkCode { get; set; } public int ScanCode { get; set; } public int Flags { get; set; } public int Time { get; set; } public int DwExtraInfo { get; set; } }
上面的代碼,對全局鉤子的使用進行了封裝,只須要建立GlobalHook類,將須要監聽處理的委託傳遞給構造函數,便可建立和使用鉤子。再來看看進程鉤子的代碼,相似於全局鉤子:
/// <summary> /// 進程鉤子,只能捕捉本進程的按鍵信息 /// </summary> internal class ProcessHook : KeyBoardHook { public override bool Install(ProcessKeyHandle clientMethod) { try { this.clientMethod = clientMethod; // 安裝鍵盤鉤子 if (hHookId == 0) { keyBoardHookProcedure = GetHookProc; hookWindowPtr = Win32API.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName); hHookId = Win32API.SetWindowsHookEx( (int)HookType.WH_KEYBOARD, keyBoardHookProcedure, IntPtr.Zero, Win32API.GetCurrentThreadId() ); //若是設置鉤子失敗. if (hHookId == 0) UnInstall(); } return true; } catch { return false; } } protected override int GetHookProc(int nCode, int wParam, IntPtr lParam) { if (nCode == 0 && nCode != 3) { bool isKeyDown = false; if (IntPtr.Size == 4) { isKeyDown = (((lParam.ToInt32() & 0x80000000) == 0)); } if (IntPtr.Size == 8) { isKeyDown = (((lParam.ToInt64() & 0x80000000) == 0)); } // 鍵盤按下 if (isKeyDown) { //Debug.WriteLine("key down_________________________"); //觸發事件把安裝信息通知客戶端 if (clientMethod != null) { //進程鉤子,按鍵值在這裏 Keys keyData = (Keys)wParam; bool handle; clientMethod(this.hookKey, keyData, out handle); if (handle)//若是處理了就直接中止 1: { Win32API.CallNextHookEx(hHookId, nCode, wParam, lParam); return 1; } } } } return Win32API.CallNextHookEx(hHookId, nCode, wParam, lParam); } }
再來看看如何在客戶端使用鉤子。只需在須要使用到鉤子的窗體中,按以下方式編寫代碼便可:
private KeyBoardHook hook; /// <summary> /// 建立進程鍵盤鉤子 /// </summary> protected void CreateProcessHook() { if (hook != null) { hook.UnInstall(); } //使用工廠類建立出對應的鉤子。 hook = KeyBoardHookHelper.CreateHook(KeyBoardHookType.Process); if (hook.Install(ClientProcessKeyHandle)) { hook.HookKey = this.GetHashCode(); } } //客戶端傳給鉤子的監聽方法。 private void ClientProcessKeyHandle(int hookKey, Keys key, out bool handle) { handle = false; if (hookKey == hook.HookKey) { OnClientProcessKeyHandle(key, out handle); } return; } /// <summary> /// 子類重寫鍵盤鉤子處理方法(系統中存在多個窗體,可將該代碼放入到窗體基類中,子類只需重寫該方法便可。) /// </summary> /// <param name="key"></param> /// <param name="handle"></param> protected virtual void OnClientProcessKeyHandle(Keys key, out bool handle) { handle = false; //截獲消息並進行處理。 if ((int)key == (int)Keys.F2)//保存, { OnSaveOrder(this.tsbtn_Save); handle = true; } }