C#使用MouseHook鼠标钩子时随机停止向事件发送输入如何解决
MouseHook随机停止触发事件问题排查与解决方案
根因分析
- 委托/实例被GC回收:非托管API
SetWindowsHookEx不会为托管对象增加引用计数,GC无法感知非托管侧仍在调用钩子委托,一旦MouseHook实例或内部的HookProc委托没有强引用被GC回收,后续钩子触发时调用无效内存地址,系统会静默卸载钩子,没有任何报错。 - 调用线程无消息循环:低级别鼠标钩子依赖调用线程的消息队列分发事件,如果你在控制台的普通线程、后台线程调用
Start,这类线程默认没有消息泵,无法处理钩子事件,触发系统超时后钩子会被自动卸载。 - 回调处理耗时过长:Windows对低级别钩子的处理时间有严格阈值(通常为数百毫秒),如果
OnMouseActivity事件回调中执行耗时操作,会被系统判定为无响应,直接卸载钩子避免阻塞全局输入。 - 类设计缺陷冲突:原代码中
hMouseHook是静态字段,多个MouseHook实例启停时会相互覆盖状态,导致钩子异常停止。
解决方案
1. 保证MouseHook实例不被GC回收
不要将MouseHook声明为局部变量,必须升级为所在类的成员变量或静态变量,全程持有强引用:
public partial class MainForm : Form { // 声明为窗体成员变量,避免被GC回收 private readonly MouseHook _mouseHook = new MouseHook(); private int _count = 0; public MainForm() { InitializeComponent(); _mouseHook.OnMouseActivity += Mouse_OnMouseActivity; _mouseHook.Start(); } private void Mouse_OnMouseActivity(object sender, MouseEventArgs e) { _count++; Console.WriteLine(_count); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { // 窗体关闭时主动释放钩子 _mouseHook.Stop(); } }
2. 非UI线程运行需加消息循环
如果是控制台程序,必须在调用Start的线程启动消息泵:
class Program { // 静态变量持有实例 private static readonly MouseHook _mouseHook = new MouseHook(); private static int _count = 0; static void Main(string[] args) { _mouseHook.OnMouseActivity += (s, e) => { _count++; Console.WriteLine(_count); }; _mouseHook.Start(); // 启动消息循环,必须加这行 System.Windows.Forms.Application.Run(); } }
3. 优化回调执行效率
所有IO、计算等耗时操作全部移到后台线程执行,不要阻塞钩子回调:
private void Mouse_OnMouseActivity(object sender, MouseEventArgs e) { // 仅做必要的轻量逻辑,耗时操作丢到线程池 ThreadPool.QueueUserWorkItem(_ => { // 这里放耗时的业务逻辑 }); }
4. 修正MouseHook类设计缺陷
修改MouseHook类的字段和逻辑,避免多实例冲突和GC回收:
public class MouseHook { private const int WM_MOUSEMOVE = 0x200; 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; public event System.Windows.Forms.MouseEventHandler OnMouseActivity; // 去掉static修饰,改为实例字段 private int hMouseHook = 0; public const int WH_MOUSE_LL = 14; // 私有字段持有委托引用 private HookProc _mouseHookProcedure; [StructLayout(LayoutKind.Sequential)] public class POINT { public int x; public int y; } [StructLayout(LayoutKind.Sequential)] public class MouseHookStruct { public POINT pt; public int hWnd; public int wHitTestCode; public int dwExtraInfo; } [DllImport("kernel32.dll")] public static extern IntPtr GetModuleHandle(string name); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern bool UnhookWindowsHookEx(int idHook); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); public MouseHook() { } ~MouseHook() { Stop(); } public void Start() { if (hMouseHook == 0) { _mouseHookProcedure = new HookProc(MouseHookProc); hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, _mouseHookProcedure, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0); if (hMouseHook == 0) { Stop(); throw new Exception("SetWindowsHookEx failed."); } // 显式告知GC委托正在使用,避免被回收 GC.KeepAlive(_mouseHookProcedure); } } public void Stop() { bool retMouse = true; if (hMouseHook != 0) { retMouse = UnhookWindowsHookEx(hMouseHook); hMouseHook = 0; } if (!(retMouse)) throw new Exception("UnhookWindowsHookEx failed."); } private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam) { if ((nCode >= 0) && (OnMouseActivity != null)) { MouseButtons button = MouseButtons.None; int clickCount = 0; switch (wParam) { case WM_LBUTTONDOWN: button = MouseButtons.Left; clickCount = 1; break; case WM_LBUTTONUP: button = MouseButtons.Left; clickCount = 2; break; case WM_LBUTTONDBLCLK: button = MouseButtons.Left; clickCount = 3; break; case WM_RBUTTONDOWN: button = MouseButtons.Right; clickCount = 4; break; case WM_RBUTTONUP: button = MouseButtons.Right; clickCount = 5; break; case WM_RBUTTONDBLCLK: button = MouseButtons.Right; clickCount = 6; break; } MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct)); System.Windows.Forms.MouseEventArgs e = new System.Windows.Forms.MouseEventArgs(button, clickCount, MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y, 0); OnMouseActivity(this, e); } return CallNextHookEx(hMouseHook, nCode, wParam, lParam); } }
内容的提问来源于stack exchange,提问作者Mega
相关产品推荐
相关产品推荐

