1. 程式人生 > >C#全域性鉤子使用

C#全域性鉤子使用

最近遇到了一個需要處理鍵盤按鍵釋放訊息的問題,我在使用重寫ProcessCmdKey之後,發現其無法響應KeyUp訊息,不知是被什麼東西攔截了,查閱了網上的一些資料,使用全域性鉤子解決了這個問題,在此把過程記錄下來。

​​​​​​​首先,在使用鉤子前我們先來了解一下要使用到的API函式。

第一步:宣告API

       //安裝鉤子
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        //解除安裝鉤子
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);

        //繼續下一個鉤子
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);

        // 取得當前執行緒編號(執行緒鉤子需要用到)
        [DllImport("kernel32.dll")]
        static extern int GetCurrentThreadId();

第二步:宣告一個委託,該委託在捕獲鍵盤訊息是會自動呼叫其對應的方法

        public event KeyEventHandler KeyUpEvent;

        public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);

        static int hKeyboardHook = 0; //宣告鍵盤鉤子處理的初始值,鉤子是否安裝成功

        public const int WH_KEYBOARD_LL = 13;   //執行緒鍵盤鉤子監聽滑鼠訊息設為2,全域性鍵盤監聽滑鼠訊息設為13
        HookProc KeyboardHookProcedure; //宣告KeyboardHookProcedure作為HookProc型別

第三步:安裝和解除安裝鉤子

         //安裝鉤子
         public void Start()
        {
            if (hKeyboardHook == 0)
            {
                KeyboardHookProcedure = new HookProc(KeyboardHookProc);
                SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure,
 GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);             
                /*關於SetWindowsHookEx的第三個引數hInstance,
                如果threadId標識當前程序建立的一個執行緒,而且子程程式碼位於當前程序,
                hInstance必須為NULL*/

                /*關於SetWindowsHookEx的第四個引數threadId,如果為全域性鉤子,
                則應該設定為0,如果是執行緒鉤子,則使用GetCurrentThreadId獲取子執行緒的ID*/
                //************************************
                //鍵盤執行緒鉤子
                //SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要監聽的執行緒idGetCurrentThreadId(),

                //************************************
                //如果SetWindowsHookEx失敗
                if (hKeyboardHook == 0)
                {
                    Stop();
                    throw new Exception("安裝鍵盤鉤子失敗");
                }
            }
        }

        //解除安裝鉤子
        public void Stop()
        {
            bool retKeyboard = true;

            if (hKeyboardHook != 0)
            {
                retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
            }

            if (!(retKeyboard)) throw new Exception("解除安裝鉤子失敗!");
        }

第四步:鉤子 子執行緒

在這之前我們先來看下子程要用的三個引數int型別的 nCode, Int32型別的 wParam, IntPtr型別的 lParam,nCode引數是鉤子程式碼,鉤子子程使用這個引數來確定任務。wParam和lParam引數包含了訊息資訊,我們可以從中提取需要的資訊,為了將這兩個引數轉成我們更容易理解的訊息,在這定義了一個結構體(對於鍵盤來說),將wParam和lParam轉化成我們更加容易看懂的資訊。

         //鍵盤結構
        [StructLayout(LayoutKind.Sequential)]
        public class KeyboardHookStruct
        {
            public int vkCode;  //定一個虛擬鍵碼。該程式碼必須有一個價值的範圍1至254
            public int scanCode; // 指定的硬體掃描碼的關鍵
            public int flags;  // 鍵標誌
            public int time; // 指定的時間戳記的這個訊息
            public int dwExtraInfo; // 指定額外資訊相關的資訊
        }
        private const int WM_KEYUP = 0x101;//KEYUP

        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            // 偵聽鍵盤事件
            if (nCode >= 0 )
            {
                KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
                // 鍵盤擡起
                if (KeyUpEvent != null&&wParam == WM_KEYUP)
                {
                    Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
                    //if (keyData == Keys.Up)
                    //{ 
                        //使用EventHandler來傳遞資料
                        KeyEventArgs e = new KeyEventArgs(keyData);
                        KeyUpEvent(this, e);
                        //MessageBox.Show("捕捉到了按鍵釋放");
                    //}
                }
            }
            //如果返回1,則結束訊息,這個訊息到此為止,不再傳遞。
            //如果返回0或呼叫CallNextHookEx函式則訊息出了這個鉤子繼續往下傳遞,也就是傳給訊息真正的接受者
            return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
        }

在上面的子程裡面,我們使用EventHandler來向外部傳遞資料,那麼我們就要先宣告一個事件

        public event KeyEventHandler KeyUpEvent;

至此,鉤子類已經基本完成,以下是整個類的程式碼:

    class KeyboardHook
    {
        public event KeyEventHandler KeyUpEvent;

        public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
        static int hKeyboardHook = 0; //宣告鍵盤鉤子處理的初始值
        public const int WH_KEYBOARD_LL = 13;   //執行緒鍵盤鉤子監聽滑鼠訊息設為2,全域性鍵盤監聽滑鼠訊息設為13
        HookProc KeyboardHookProcedure; //宣告KeyboardHookProcedure作為HookProc型別

        //鍵盤結構
        [StructLayout(LayoutKind.Sequential)]
        public class KeyboardHookStruct
        {
            public int vkCode;  //定一個虛擬鍵碼。該程式碼必須有一個價值的範圍1至254
            public int scanCode; // 指定的硬體掃描碼的關鍵
            public int flags;  // 鍵標誌
            public int time; // 指定的時間戳記的這個訊息
            public int dwExtraInfo; // 指定額外資訊相關的資訊
        }

        //使用此功能,安裝了一個鉤子
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        //呼叫此函式解除安裝鉤子
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);

        //使用此功能,通過資訊鉤子繼續下一個鉤子
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);

        // 取得當前執行緒編號(執行緒鉤子需要用到)
        [DllImport("kernel32.dll")]
        static extern int GetCurrentThreadId();

        public void Start()
        {
            // 安裝鍵盤鉤子
            if (hKeyboardHook == 0)
            {
                KeyboardHookProcedure = new HookProc(KeyboardHookProc);
                SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);
                //************************************
                //鍵盤執行緒鉤子
                //SetWindowsHookEx(13, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要監聽的執行緒idGetCurrentThreadId(),

                //************************************
                //如果SetWindowsHookEx失敗
                if (hKeyboardHook == 0)
                {
                    Stop();
                    throw new Exception("安裝鍵盤鉤子失敗");
                }
            }
        }
        public void Stop()
        {
            bool retKeyboard = true;


            if (hKeyboardHook != 0)
            {
                retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
            }

            if (!(retKeyboard)) throw new Exception("解除安裝鉤子失敗!");
        }
        private const int WM_KEYUP = 0x101;//KEYUP

        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            // 偵聽鍵盤事件
            if (nCode >= 0 )
            {
                KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
                // 鍵盤擡起
                if (KeyUpEvent != null&&wParam == WM_KEYUP)
                {
                    Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
                    //if (keyData == Keys.Up)
                    //{
                        KeyEventArgs e = new KeyEventArgs(keyData);
                        KeyUpEvent(this, e);
                        //MessageBox.Show("捕捉到了按鍵釋放");
                    //}
                }
            }
            //如果返回1,則結束訊息,這個訊息到此為止,不再傳遞。
            //如果返回0或呼叫CallNextHookEx函式則訊息出了這個鉤子繼續往下傳遞,也就是傳給訊息真正的接受者
            return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
        }
        ~KeyboardHook()
        {
            Stop();
        }
    }

第五步:接下來就是呼叫了。

首先我們宣告資料傳遞事件 並且初始化鉤子類

        private KeyEventHandler myKeyEventHandeler = null;//按鍵鉤子
        private KeyboardHook k_hook = new KeyboardHook();

接下來,寫兩個函式分別負責安裝和解除安裝鉤子,其中hook_KeyUp是鉤子捕捉到訊息後具體需要實現的方法

         public void start()
         {
           myKeyEventHandeler = new KeyEventHandler(hook_KeyUp);
           k_hook.KeyUpEvent += myKeyEventHandeler;//鉤住鍵按下
           k_hook.Start();//安裝鍵盤鉤子
         }

        public void stop()
         {
           if (myKeyEventHandeler != null)
           {
               k_hook.KeyUpEvent -= myKeyEventHandeler;//取消按鍵事件
               myKeyEventHandeler = null;
               k_hook.Stop();//關閉鍵盤鉤子
           }
         }
         
        private void hook_KeyUp(object sender, KeyEventArgs e)
        {
            //  這裡寫具體實現
            textBox1.Text = "捕捉到了" + e.KeyCode.ToString() + "鍵釋放";
        }

總結:突然發現沒什麼好說的,該說的都在上面了....