全局鼠标键盘钩子的使用-飞外

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

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

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

鼠标钩子:

 1 class MouseHook 3 private Point point; 4 private Point Point 6 get { return point; } 7 set 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; 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 } 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 } 51 /// summary  52 /// 卸载鼠标钩子 53 /// /summary  54 public void UnHook() 55 { 56 Win32Api.UnhookWindowsHookEx(hHook); 57 } 59 /// summary  60 /// 执行鼠标钩子 61 /// /summary  62 /// param  /param  63 /// param  /param  64 /// param  /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 }111 this.Point = new Point(MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y);112 return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);113 }114 }117 [StructLayout(LayoutKind.Sequential)]118 public class POINT119 {120 public int x;122 public int y;124 }125 [StructLayout(LayoutKind.Sequential)]126 public class MouseHookStruct127 {129 public POINT pt;131 public int hwnd;133 public int wHitTestCode;135 public int dwExtraInfo;137 }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 3 public event KeyEventHandler KeyDownEvent; 4 public event KeyPressEventHandler KeyPressEvent; 5 public event KeyEventHandler KeyUpEvent; 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 //键盘结构 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 } 25 // 取得当前线程编号(线程钩子需要用到) 26 [DllImport("kernel32.dll")] 27 static extern int GetCurrentThreadId(); 29 //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效 30 [DllImport("kernel32.dll")] 31 public static extern IntPtr GetModuleHandle(string name); 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 } 57 /// summary  58 /// 卸载键盘钩子 59 /// /summary  60 public void Stop() 61 { 62 bool retKeyboard = true; 65 if (hKeyboardHook != 0) 66 { 67 retKeyboard = Win32Api.UnhookWindowsHookEx(hKeyboardHook); 68 hKeyboardHook = 0; 69 } 71 if (!(retKeyboard)) throw new Exception("卸载钩子失败!"); 72 } 73 //ToAscii职能的转换指定的虚拟键码和键盘状态的相应字符或字符 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); 88 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 89 private static extern short GetKeyState(int vKey); 92 /// summary  93 /// 执行键盘钩子 94 /// /summary  95 /// param  /param  96 /// param  /param  97 /// param  /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 }113 //键盘点击114 if (KeyPressEvent != null (Int32)wParam == (Int32)KeyEvent.WM_KEYDOWN)115 {116 byte[] keyState = new byte[256];117 GetKeyboardState(keyState);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 }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 }135 }136 //如果返回1,则结束消息,这个消息到此为止,不再传递。137 //如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者138 return Win32Api.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);139 }141 ~KeyHook()142 {143 Stop();144 }147 }
View Code第三步、点击调用鼠标键盘钩子:

点击调用鼠标钩子

 1 //鼠标事件监听 2 public partial class MouseEventHook 4 MouseHook mh; //定义全局的鼠标监听方法 5 Point p1 = new Point(0, 0); //定义全局鼠标坐标 6 Point p2 = new Point(0, 0); //多定义个坐标可以显示鼠标按下与抬起之间移动的距离,这个也可不要 8 //点击按钮开始鼠标监听 9 private void button1_Click(object sender, EventArgs e)11 mh = new MouseHook();12 mh.SetHook(); //安装鼠标钩子14 mh.MouseMoveEvent += my_MouseMoveEvent; //绑定鼠标移动时触发的事件15 mh.MouseDownEvent += my_MouseDownEvent; //绑定鼠标按下时触发的事件16 mh.MouseUpEvent += my_MouseUpEvent; //绑定鼠标抬起时触发的事件20 //点击按钮停止鼠标监听21 private void button2_Click(object sender, EventArgs e)23 if (mh != null) mh.UnHook();24 MessageBox.Show("鼠标监听停止!");29 // 鼠标移动触发的事件30 private void my_MouseMoveEvent(object 31 sender,MouseEventArgs e)33 Point p = e.Location; //获取坐标34 string movexy = p.ToString();35 richTextBox1.AppendText(movexy + "," + stopWatch.ElapsedMilliseconds + ""); //将坐标记录到RichTextBox控件中39 //按下鼠标键触发的事件40 private void my_MouseDownEvent(object sender, MouseEventArgs e)42 if (e.Button == MouseButtons.Left)44 LeftTag = true;45 richTextBox1.AppendText("按下了左键"); //将按键操作记录到RichTextBox控件中47 if (e.Button == MouseButtons.Right)49 RightTag = true;50 richTextBox1.AppendText("按下了右键52 p1 = e.Location;57 //松开鼠标键触发的事件58 private void my_MouseUpEvent(object sender, MouseEventArgs e)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)64 richTextBox1.AppendText("松开了左键 " + LineNum + "67 if (e.Button == MouseButtons.Right)69 richTextBox1.AppendText("松开了右键 " + LineNum + "71 richTextBox1.AppendText("移动了" + value + "距离72 RightTag = false;73 LeftTag = false;74 p1 = new Point(0, 0);75 p2 = new Point(0, 0);80 } 
View Code

点击调用键盘钩子

 1 public partial class KeyEventHook 3 KeyHook k_hook; 4 KeyEventHandler myKeyDownHandeler; 5 KeyEventHandler myKeyUpHandeler; 7 /// summary  8 /// 开始键盘监听 9 /// /summary 10 public void startKeyListen()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();//安装键盘钩子20 /// summary 21 /// 结束键盘监听22 /// /summary 23 public void stopKeyListen()25 if (myKeyDownHandeler != null)27 k_hook.KeyDownEvent -= myKeyDownHandeler;//取消按键事件28 myKeyDownHandeler = null;29 k_hook.Stop();//关闭键盘钩子35 /// summary 36 /// 键盘按下时就调用这个37 /// /summary 38 /// param  /param 39 /// param  /param 40 public void hook_KeyDown(object sender, KeyEventArgs e)42 if (e.KeyCode.Equals(Keys.Escape)) //如果按下Esc键可执行if里面的操作44 //按下特定键后执行的代码46 Log.LogMouseEvent(e.KeyCode.ToString() + ",键盘按下" ); //这个Log.LogMouseEvent是我用来记录键盘按下抬起的日志,50 /// summary 51 /// 有键盘抬起时就调用这个52 /// /summary 53 /// param  /param 54 /// param  /param 55 public void hook_KeyUp(object sender, KeyEventArgs e)57 Log.LogMouseEvent(e.KeyCode.ToString() + ",键盘抬起"); //这个Log.LogMouseEvent是我用来记录键盘按下抬起的日志60 }
View Code

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

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