zoukankan      html  css  js  c++  java
  • 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;
                }
            }
    客户端调用
  • 相关阅读:
    Python Install for windows X64
    CentOS 7 Install Gitlab CE
    centos 7安装vmtools时提示The path "" is not a valid path to the xxx kernel headers.
    Jenkins install
    gradle 编译 No such property: sonatypeUsername错误解决
    Hololens 开发环境配置(转)
    Krapo 2
    Krapno 1
    dubbo 常见错误
    groupby
  • 原文地址:https://www.cnblogs.com/YangFengHui/p/4609112.html
Copyright © 2011-2022 走看看