C#實現鍵盤鉤子

前言: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;
            }
        }
客戶端調用
相關文章
相關標籤/搜索