C#实现键盘钩子

前言:

  因为项目中需要使用到快捷键,所以上网找资料了解关于快捷键的实现技术,于是有了键盘钩子的使用学习。在网上了解到,键盘钩子其实只是很多种钩子中的其中一种。所谓钩子:请看下面关于钩子的描述(来自百度百科):

Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。

钩子可以监视系统或进程中的各种事件消息,截获发往目标窗口的消息并进行处理。这样,我们就可以在系统中安装自定义的钩子,监视系统中特定事件的发生,完成特定的功能,比如截获键盘、鼠标的输入,屏幕取词,日志监视等等。

钩子的本质是一段用以处理系统消息的程序,通过系统调用,将其挂入系统。钩子的种类有很多,每种钩子可以截获并处理相应的消息,每当特定的消息发出,在到达目的窗口之前,钩子程序先行截获该消息、得到对此消息的控制权。此时在钩子函数中就可以对截获的消息进行加工处理,甚至可以强制结束消息的传递。

 

本文我们主要来谈谈全局钩子和进程钩子的使用。

  全局钩子:全局钩子,能够截获所有运行在操作系统上的程序发送的消息,但是因其全局性,钩子安装之后,会比较损耗性能,在使用完毕之后,必须实时的卸载。

     进程钩子:可以针对某一个进程,仅仅截获某一个应用程序的消息,比较具有针对性,适用于普通的信息管理系统。

    钩子程序是封装在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);
钩子相关方法的导入

在系统使用中,我们对钩子进行了简单的封装,针对全局钩子和进程钩子共有的特性,抽象出钩子基类

/// <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;
            }
        }
客户端调用

 

posted @ 2015-06-29 23:04  GY小小鸟  阅读(1821)  评论(0编辑  收藏  举报