全局鼠标键盘钩子的使用

前言:

  windows中大部分的应用程序都是基于消息机制的,它们都有一个消息过程函数,根据不同的消息完成不同的功能。而消息钩子是windows提供的一种消息过滤和预处理机制,可以用来截获和监视系统中的消息。按照钩子作用范围不同,又可以分为局部钩子和全局钩子。局部钩子是针对某个线程的,而全局钩子是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL文件中实现相应的钩子函数。

由于windows操作是基于消息的,那么鼠标和键盘的操作也是通过消息传递到目标窗口的,因此可以按装全局鼠标键盘钩子,使鼠标键盘在传递到目标窗口之前拦截、钩住它,为我们提供一个做操作的机会,可以触发某个事件方法,可以阻止它传递、也可以不做任何处理。

不管是鼠标钩子还是键盘钩子,都需要先注册windows全局钩子。

 

方法:

 首先需要调用user32.dll包

SetWindowsHookEx:安装全局钩子

UnhookWindowsHookEx:卸载全局钩子

CallNextHookEx:调用下一个钩子

 

参数:

private static int hMouseHook = 0;    

private const int WM_MOUSEMOVE = 0x200; //鼠标移动,本次没有使用这个参数,而是用的MouseEventArgs()方法监听鼠标移动
private const int WM_LBUTTONDOWN = 0x201;   //左键按下
private const int WM_RBUTTONDOWN = 0x204;    //右键按下
private const int WM_MBUTTONDOWN = 0x207;    //中键按下
private const int WM_LBUTTONUP = 0x202;    //左键抬起
private const int WM_RBUTTONUP = 0x205;    //右键抬起
private const int WM_MBUTTONUP = 0x208;    //中键抬起

private const int WM_LBUTTONDBLCLK = 0x203;  //左键单击,本次未使用
private const int WM_RBUTTONDBLCLK = 0x206;  //右键单击,本次未使用
private const int WM_MBUTTONDBLCLK = 0x209;  //中键单击,本次未使用

 

 

完整流程:

第一步、定义windows全局钩子类:

 1     public class Win32Api
 2     {
 3 
 4         public delegate int HookProc( int nCode, IntPtr wParam, IntPtr lParam);
 5 
 6         /// <summary>
 7         /// 安装钩子。把一个应用程序定义的钩子子程安装到钩子链表中。函数成功则返回钩子子程的句柄,失败返回NULL
 8         /// </summary>
 9         /// <param name="idHook">钩子的类型。它决定了 HOOKPROC 被调用的时机</param>
10         /// <param name="lpfn">指向钩子回调函数的指针。如果最后一个参数 dwThreadId 为0或者是其它进程创建的线程标识符,则 lpfn 参数必须指向DLL中的钩子回调函数,即 HOOKPROC 函数必须在DLL中实现。否则,lpfn 可以指向与当前进程相关联的代码中的钩子过程</param>
11         /// <param name="hInstance">包含由 lpfn 参数指向的钩子回调函数的DLL句柄。如果 dwThreadId 参数指定由当前进程创建线程,并且钩子回调函数位于当前进程关联的代码中,则 hmod 参数必须设置为 NULL。</param>
12         /// <param name="threadId">与钩子程序关联的线程标识符(指定要 HOOK 的线程 ID)。如果此参数为0,则钩子过程与系统中所有线程相关联,即全局消息钩子</param>
13         /// <returns></returns>
14         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
15         public static extern int SetWindowsHookEx(int idHook,HookProc lpfn,IntPtr hInstance, int threadId);
16 
17         /// <summary>
18         /// 卸载钩子。函数成功则返回非0,失败返回NULL
19         /// </summary>
20         /// <param name="idHook">钩子的类型</param>
21         /// <returns></returns>
22         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
23         public static extern bool UnhookWindowsHookEx(int idHook);
24 
25         /// <summary>
26         /// 调用下一个钩子。调用钩子链中的下一个挂钩过程,调用成功返回值是下一个钩子的回调函数,否则为0。当前钩子程序也必须返回此值。
27         /// </summary>
28         /// <param name="idHook">钩子的类型</param>
29         /// <param name="nCode">钩子代码。就是给下一个钩子要交待的内容</param>
30         /// <param name="wParam">要传递的参数。由钩子类型决定是什么参数</param>
31         /// <param name="lParam">要传递的参数。由钩子类型决定是什么参数</param>
32         /// <returns></returns>
33         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
34         public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
35 
36     }
View Code

注:鼠标钩子函数和键盘钩子函数都会公用上面这个全局钩子类

 

第二步、定义鼠标钩子方法和键盘钩子方法

鼠标钩子:

  1 class MouseHook
  2     {
  3         private Point point;
  4         private Point Point
  5         {
  6             get { return point; }
  7             set
  8             {
  9                 if (point != value)
 10                 {
 11                     point = value;
 12                     if (MouseMoveEvent != null)
 13                     {
 14                         var e = new MouseEventArgs(MouseButtons.None, 0, point.X, point.Y, 0);
 15                         MouseMoveEvent(this, e);
 16                     }
 17                 }
 18             }
 19         }
 20         private int hHook;
 21         private static int hMouseHook = 0;
 22         private const int WM_MOUSEMOVE = 0x200;
 23         private const int WM_LBUTTONDOWN = 0x201;
 24         private const int WM_RBUTTONDOWN = 0x204;
 25         private const int WM_MBUTTONDOWN = 0x207;
 26         private const int WM_LBUTTONUP = 0x202;
 27         private const int WM_RBUTTONUP = 0x205;
 28         private const int WM_MBUTTONUP = 0x208;
 29         private const int WM_LBUTTONDBLCLK = 0x203;
 30         private const int WM_RBUTTONDBLCLK = 0x206;
 31         private const int WM_MBUTTONDBLCLK = 0x209;
 32 
 33         public const int WH_MOUSE_LL = 14; //idHook值的参数,14为系统级,截获全局鼠标消息。详细SetWindowsHookEx函数的idHook参照https://www.cnblogs.com/ndyxb/p/12883292.html
 34         public Win32Api.HookProc hProc;
 35         public MouseHook()
 36         {
 37             this.Point = new Point();
 38         }
 39 
 40         /// <summary>
 41         /// 安装鼠标钩子
 42         /// </summary>
 43         /// <returns></returns>
 44         public int SetHook()
 45         {
 46             hProc = new Win32Api.HookProc(MouseHookProc);
 47             hHook = Win32Api.SetWindowsHookEx(WH_MOUSE_LL, hProc, IntPtr.Zero, 0);
 48             return hHook;
 49         }
 50 
 51         /// <summary>
 52         /// 卸载鼠标钩子
 53         /// </summary>
 54         public void UnHook()
 55         {
 56             Win32Api.UnhookWindowsHookEx(hHook);
 57         }
 58 
 59         /// <summary>
 60         /// 执行鼠标钩子
 61         /// </summary>
 62         /// <param name="nCode"></param>
 63         /// <param name="wParam"></param>
 64         /// <param name="lParam"></param>
 65         /// <returns></returns>
 66         private int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
 67         {
 68             MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
 69             if (nCode < 0)
 70             {
 71                 return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);
 72             }
 73             else
 74             {
 75                 MouseButtons button = MouseButtons.None;
 76                 int clickCount = 0;
 77                 switch ((Int32)wParam)
 78                 {
 79                     case WM_LBUTTONDOWN:
 80                         button = MouseButtons.Left;
 81                         clickCount = 1;
 82                         MouseDownEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
 83                         break;
 84                     case WM_RBUTTONDOWN:
 85                         button = MouseButtons.Right;
 86                         clickCount = 1;
 87                         MouseDownEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
 88                         break;
 89                     case WM_MBUTTONDOWN:
 90                         button = MouseButtons.Middle;
 91                         clickCount = 1;
 92                         MouseDownEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
 93                         break;
 94                     case WM_LBUTTONUP:
 95                         button = MouseButtons.Left;
 96                         clickCount = 1;
 97                         MouseUpEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
 98                         break;
 99                     case WM_RBUTTONUP:
100                         button = MouseButtons.Right;
101                         clickCount = 1;
102                         MouseUpEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
103                         break;
104                     case WM_MBUTTONUP:
105                         button = MouseButtons.Middle;
106                         clickCount = 1;
107                         MouseUpEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
108                         break;
109                 }
110 
111                 this.Point = new Point(MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y);
112                 return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);
113             }
114         }
115 
116 
117         [StructLayout(LayoutKind.Sequential)]
118         public class POINT
119         {
120             public int x;
121 
122             public int y;
123 
124         }
125         [StructLayout(LayoutKind.Sequential)]
126         public class MouseHookStruct
127         {
128 
129             public POINT pt;
130 
131             public int hwnd;
132 
133             public int wHitTestCode;
134 
135             public int dwExtraInfo;
136 
137         }
138 
139 
140 
141 
142         public delegate void MouseMoveHandler(object sender, MouseEventArgs e);
143         public event MouseMoveHandler MouseMoveEvent;
144         public delegate void MouseDownHandler(object sender, MouseEventArgs e);
145         public event MouseDownHandler MouseDownEvent;
146         public delegate void MouseUpHandler(object sender, MouseEventArgs e);
147         public event MouseUpHandler MouseUpEvent;
148     }
View Code

键盘钩子:

  1 class KeyHook
  2     {
  3         public event KeyEventHandler KeyDownEvent;
  4         public event KeyPressEventHandler KeyPressEvent;
  5         public event KeyEventHandler KeyUpEvent;
  6 
  7         
  8         static int hKeyboardHook = 0; //声明键盘钩子处理的初始值
  9                                       //值在Microsoft SDK的Winuser.h里查询
 10         public const int WH_KEYBOARD_LL = 13;   //线程键盘钩子监听鼠标消息设为2,全局键盘监听鼠标消息设为13
 11         Win32Api.HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型
 12                                                     //键盘结构
 13 
 14         [StructLayout(LayoutKind.Sequential)]
 15         public class KeyboardHookStruct
 16         {
 17             public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254
 18             public int scanCode; // 指定的硬件扫描码的关键
 19             public int flags;  // 键标志
 20             public int time; // 指定的时间戳记的这个讯息
 21             public int dwExtraInfo; // 指定额外信息相关的信息
 22         }
 23         
 24 
 25         // 取得当前线程编号(线程钩子需要用到)
 26         [DllImport("kernel32.dll")]
 27         static extern int GetCurrentThreadId();
 28 
 29         //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效
 30         [DllImport("kernel32.dll")]
 31         public static extern IntPtr GetModuleHandle(string name);
 32 
 33         /// <summary>
 34         /// 安装键盘钩子
 35         /// </summary>
 36         public void Start()
 37         {
 38             // 安装键盘钩子
 39             if (hKeyboardHook == 0)
 40             {
 41                 KeyboardHookProcedure = new Win32Api.HookProc(KeyboardHookProc);
 42                 hKeyboardHook = Win32Api.SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);
 43                 //hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
 44                 //************************************
 45                 //键盘线程钩子
 46                 Win32Api.SetWindowsHookEx(13, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要监听的线程idGetCurrentThreadId(),
 47                                                                                                //键盘全局钩子,需要引用空间(using System.Reflection;)
 48                                                                                                //如果SetWindowsHookEx失败
 49                 if (hKeyboardHook == 0)
 50                 {
 51                     Stop();
 52                     throw new Exception("安装键盘钩子失败");
 53                 }
 54             }
 55         }
 56 
 57         /// <summary>
 58         /// 卸载键盘钩子
 59         /// </summary>
 60         public void Stop()
 61         {
 62             bool retKeyboard = true;
 63 
 64 
 65             if (hKeyboardHook != 0)
 66             {
 67                 retKeyboard = Win32Api.UnhookWindowsHookEx(hKeyboardHook);
 68                 hKeyboardHook = 0;
 69             }
 70             
 71             if (!(retKeyboard)) throw new Exception("卸载钩子失败!");
 72         }
 73         //ToAscii职能的转换指定的虚拟键码和键盘状态的相应字符或字符
 74 
 75 
 76 
 77 
 78         [DllImport("user32")]
 79         public static extern int ToAscii(int uVirtKey, //[in] 指定虚拟关键代码进行翻译。
 80                                          int uScanCode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压)
 81                                          byte[] lpbKeyState, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如果设置表明,关键是对切换。在此功能,只有肘位的CAPS LOCK键是相关的。在切换状态的NUM个锁和滚动锁定键被忽略。
 82                                          byte[] lpwTransKey, // [out] 指针的缓冲区收到翻译字符或字符。
 83                                          int fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.
 84         //获取按键的状态
 85         [DllImport("user32")]
 86         public static extern int GetKeyboardState(byte[] pbKeyState);
 87 
 88         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
 89         private static extern short GetKeyState(int vKey);
 90 
 91 
 92         /// <summary>
 93         /// 执行键盘钩子
 94         /// </summary>
 95         /// <param name="nCode"></param>
 96         /// <param name="wParam"></param>
 97         /// <param name="lParam"></param>
 98         /// <returns></returns>
 99         private int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
100         {
101             // 侦听键盘事件
102             if ((nCode >= 0) && (KeyDownEvent != null || KeyUpEvent != null || KeyPressEvent != null))
103             {
104                 KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
105                 // 键盘按下
106                 if (KeyDownEvent != null && ((Int32)wParam == (Int32)KeyEvent.WM_KEYDOWN || (Int32)wParam == (Int32)KeyEvent.WM_SYSKEYDOWN))
107                 {
108                     Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
109                     KeyEventArgs e = new KeyEventArgs(keyData);
110                     KeyDownEvent(this, e);
111                 }
112 
113                 //键盘点击
114                 if (KeyPressEvent != null && (Int32)wParam == (Int32)KeyEvent.WM_KEYDOWN)
115                 {
116                     byte[] keyState = new byte[256];
117                     GetKeyboardState(keyState);
118 
119                     byte[] inBuffer = new byte[2];
120                     if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
121                     {
122                         KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);
123                         KeyPressEvent(this, e);
124                     }
125                 }
126 
127                 // 键盘抬起
128                 if (KeyUpEvent != null && ((Int32)wParam == (Int32)KeyEvent.WM_KEYUP || (Int32)wParam == (Int32)KeyEvent.WM_SYSKEYUP))
129                 {
130                     Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
131                     KeyEventArgs e = new KeyEventArgs(keyData);
132                     KeyUpEvent(this, e);
133                 }
134 
135             }
136             //如果返回1,则结束消息,这个消息到此为止,不再传递。
137             //如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者
138             return Win32Api.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
139         }
140 
141         ~KeyHook()
142         {
143             Stop();
144         }
145 
146       
147     }
View Code

 

第三步、点击调用鼠标键盘钩子:

点击调用鼠标钩子

 1 //鼠标事件监听
 2 public partial class MouseEventHook
 3 {
 4     MouseHook mh;     //定义全局的鼠标监听方法
 5     Point p1 = new Point(0, 0);   //定义全局鼠标坐标
 6     Point p2 = new Point(0, 0);   //多定义个坐标可以显示鼠标按下与抬起之间移动的距离,这个也可不要
 7 
 8     //点击按钮开始鼠标监听
 9     private void button1_Click(object sender, EventArgs e)
10     {
11            mh = new MouseHook();
12            mh.SetHook();     //安装鼠标钩子
13 
14            mh.MouseMoveEvent += my_MouseMoveEvent;     //绑定鼠标移动时触发的事件
15            mh.MouseDownEvent += my_MouseDownEvent;    //绑定鼠标按下时触发的事件
16            mh.MouseUpEvent += my_MouseUpEvent;            //绑定鼠标抬起时触发的事件
17     }
18 
19     
20     //点击按钮停止鼠标监听
21     private void button2_Click(object sender, EventArgs e)
22     {
23         if (mh != null) mh.UnHook();
24         MessageBox.Show("鼠标监听停止!");
25     }
26 
27 
28 
29         // 鼠标移动触发的事件
30         private void my_MouseMoveEvent(object 
31  sender,MouseEventArgs e)
32         {
33             Point p = e.Location;   //获取坐标
34             string movexy = p.ToString();
35             richTextBox1.AppendText(movexy + "," + stopWatch.ElapsedMilliseconds + "\n");   //将坐标记录到RichTextBox控件中
36         }
37 
38 
39          //按下鼠标键触发的事件
40         private void my_MouseDownEvent(object sender, MouseEventArgs e)
41         {
42             if (e.Button == MouseButtons.Left)
43             {
44                 LeftTag = true;
45                 richTextBox1.AppendText("按下了左键\n");    //将按键操作记录到RichTextBox控件中
46             }
47             if (e.Button == MouseButtons.Right)
48             {
49                 RightTag = true;
50                 richTextBox1.AppendText("按下了右键\n");
51             }
52             p1 = e.Location;
53             
54         }
55 
56 
57         //松开鼠标键触发的事件
58         private void my_MouseUpEvent(object sender, MouseEventArgs e)
59         {
60             p2 = e.Location;
61             double value = Math.Sqrt(Math.Abs(p1.X - p2.X) * Math.Abs(p1.X - p2.X) + Math.Abs(p1.Y - p2.Y) * Math.Abs(p1.Y - p2.Y));
62             if (e.Button == MouseButtons.Left)
63             {
64                 richTextBox1.AppendText("松开了左键  " + LineNum + "\n");
65 
66             }
67             if (e.Button == MouseButtons.Right)
68             {
69                 richTextBox1.AppendText("松开了右键  " + LineNum + "\n");
70             }
71             richTextBox1.AppendText("移动了" + value + "距离\n");
72             RightTag = false;
73             LeftTag = false;
74             p1 = new Point(0, 0);
75             p2 = new Point(0, 0);
76         }
77 
78 
79 
80 }    
View Code

 点击调用键盘钩子

 1 public partial class KeyEventHook
 2 {
 3         KeyHook k_hook;
 4         KeyEventHandler myKeyDownHandeler;
 5         KeyEventHandler myKeyUpHandeler;
 6         
 7         /// <summary>
 8         /// 开始键盘监听
 9         /// </summary>
10         public void startKeyListen()
11         {
12             k_hook = new KeyHook();
13             myKeyDownHandeler = new KeyEventHandler(hook_KeyDown);
14             k_hook.KeyDownEvent += myKeyDownHandeler;//钩住键盘按下
15             myKeyUpHandeler = new KeyEventHandler(hook_KeyUp);
16             k_hook.KeyUpEvent += myKeyUpHandeler;//钩住键盘抬起
17             k_hook.Start();//安装键盘钩子
18         }
19 
20         /// <summary>
21         /// 结束键盘监听
22         /// </summary>
23         public void stopKeyListen()
24         {
25             if (myKeyDownHandeler != null)
26             {
27                 k_hook.KeyDownEvent -= myKeyDownHandeler;//取消按键事件
28                 myKeyDownHandeler = null;
29                 k_hook.Stop();//关闭键盘钩子
30             }
31         }
32 
33 
34 
35         /// <summary>
36         /// 键盘按下时就调用这个
37         /// </summary>
38         /// <param name="sender"></param>
39         /// <param name="e"></param>
40         public void hook_KeyDown(object sender, KeyEventArgs e)
41         {
42             if (e.KeyCode.Equals(Keys.Escape))         //如果按下Esc键可执行if里面的操作
43             {
44                        //按下特定键后执行的代码
45             }
46             Log.LogMouseEvent(e.KeyCode.ToString() + ",键盘按下" );      //这个Log.LogMouseEvent是我用来记录键盘按下抬起的日志,
47             
48         }
49 
50         /// <summary>
51         /// 有键盘抬起时就调用这个
52         /// </summary>
53         /// <param name="sender"></param>
54         /// <param name="e"></param>
55         public void hook_KeyUp(object sender, KeyEventArgs e)
56         {
57              Log.LogMouseEvent(e.KeyCode.ToString() + ",键盘抬起");      //这个Log.LogMouseEvent是我用来记录键盘按下抬起的日志
58                 
59         }
60 }
View Code

 

 

总结:

监听流程为:安装钩子——》触发钩子后调用相应的绑定事件——》调用下一个钩子——》监听完成,卸载钩子

钩子在程序退出之后会自动卸载,不过那样容易出错而且不好控制,最好还是手动卸载

END

posted @ 2021-03-09 15:09  jonykuku  阅读(1654)  评论(0编辑  收藏  举报