You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 02:39:03