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

C#控制台程序实现全局鼠标钩子(无需WinForms)

在C#控制台应用中实现全局鼠标钩子(不依赖WinForms)

你说得对,控制台程序完全没必要依赖WinForms的Application.Run()——毕竟低级全局鼠标钩子(WH_MOUSE_LL)本质上只需要一个消息循环就能正常工作,我们完全可以用WinAPI自己实现这个循环,不用引入WinForms的依赖。

核心思路

WH_MOUSE_LL钩子的回调函数是在调用线程的消息队列里被触发的,所以只要我们在控制台主线程里手动启动一个消息循环,就能让钩子正常接收鼠标事件。我们可以用GetMessage、TranslateMessage和DispatchMessage这三个WinAPI函数来实现消息循环,替代Application.Run()。

另外,为了优雅地退出程序并卸载钩子,我们可以监听控制台的Ctrl+C退出事件,确保钩子被正确释放,避免资源泄漏。

修改后的完整代码

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class GlobalMouseHook
{
    private static LowLevelMouseProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;
    // 用于控制消息循环的退出
    private static bool _isRunning = true;

    public static void Main()
    {
        // 设置钩子
        _hookID = SetHook(_proc);
        Console.WriteLine("全局鼠标钩子已启动,按Ctrl+C退出...");

        // 注册Ctrl+C退出事件
        Console.CancelKeyPress += (sender, e) =>
        {
            e.Cancel = true;
            _isRunning = false;
            // 发送退出消息终止消息循环
            PostQuitMessage(0);
        };

        // 自定义消息循环,替代Application.Run()
        MSG msg;
        while (_isRunning && GetMessage(out msg, IntPtr.Zero, 0, 0) > 0)
        {
            TranslateMessage(ref msg);
            DispatchMessage(ref msg);
        }

        // 卸载钩子
        UnhookWindowsHookEx(_hookID);
        Console.WriteLine("钩子已卸载,程序退出");
    }

    private static IntPtr SetHook(LowLevelMouseProc proc)
    {
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        {
            return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
        }
    }

    private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && MouseMessages.WM_MOUSEMOVE == (MouseMessages)wParam)
        {
            MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
            Console.WriteLine($"鼠标坐标:X={hookStruct.pt.x}, Y={hookStruct.pt.y}");
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

    #region WinAPI定义
    private const int WH_MOUSE_LL = 14;

    private enum MouseMessages
    {
        WM_LBUTTONDOWN = 0x0201,
        WM_LBUTTONUP = 0x0202,
        WM_MOUSEMOVE = 0x0200,
        WM_MOUSEWHEEL = 0x020A,
        WM_RBUTTONDOWN = 0x0204,
        WM_RBUTTONUP = 0x0205
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int x;
        public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MSLLHOOKSTRUCT
    {
        public POINT pt;
        public uint mouseData;
        public uint flags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct MSG
    {
        public IntPtr hwnd;
        public uint message;
        public IntPtr wParam;
        public IntPtr lParam;
        public uint time;
        public POINT pt;
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool TranslateMessage(ref MSG lpMsg);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr DispatchMessage(ref MSG lpMsg);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern void PostQuitMessage(int nExitCode);
    #endregion
}

关键改动说明

  1. 自定义消息循环:用GetMessage循环处理消息,直到收到退出信号,完全替代Application.Run(),不需要引用WinForms。
  2. 优雅退出处理:监听Console.CancelKeyPress事件,在用户按Ctrl+C时设置退出标记并发送PostQuitMessage终止消息循环,同时卸载钩子。
  3. 移除WinForms依赖:整个代码只依赖System、System.Diagnostics和System.Runtime.InteropServices,都是控制台程序默认的引用。

运行这个程序后,你就能在控制台实时看到鼠标移动的坐标,按Ctrl+C可以安全退出并卸载钩子。

内容的提问来源于stack exchange,提问作者Nikita

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:07:42