全局鼠标键盘钩子的使用
前言:
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 }
注:鼠标钩子函数和键盘钩子函数都会公用上面这个全局钩子类
第二步、定义鼠标钩子方法和键盘钩子方法
鼠标钩子:
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 }
键盘钩子:
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 }
第三步、点击调用鼠标键盘钩子:
点击调用鼠标钩子
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 }
点击调用键盘钩子
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 }
总结:
监听流程为:安装钩子——》触发钩子后调用相应的绑定事件——》调用下一个钩子——》监听完成,卸载钩子
钩子在程序退出之后会自动卸载,不过那样容易出错而且不好控制,最好还是手动卸载