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

如何不使用GetAsyncKeyState检测左键按下?mouse_event干扰问题求助

解决方案:替代GetAsyncKeyState检测鼠标左键+避免模拟点击干扰状态检测

一、替代GetAsyncKeyState检测鼠标左键按下状态

可以通过全局鼠标钩子直接监听系统鼠标消息,维护物理左键的按下状态,完全不依赖GetAsyncKeyState,且不受模拟输入干扰。

实现代码

using System;
using System.Runtime.InteropServices;

public class MouseHook
{
    private const int WH_MOUSE_LL = 14;
    private const int WM_LBUTTONDOWN = 0x0201;
    private const int WM_LBUTTONUP = 0x0202;

    private static LowLevelMouseProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;
    public static bool IsLeftMouseDown { get; private set; } = false;

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

    [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);

    public static void SetHook()
    {
        using (var curProcess = System.Diagnostics.Process.GetCurrentProcess())
        using (var curModule = curProcess.MainModule)
        {
            _hookID = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(curModule.ModuleName), 0);
        }
    }

    public static void Unhook()
    {
        UnhookWindowsHookEx(_hookID);
    }

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0)
        {
            switch ((int)wParam)
            {
                case WM_LBUTTONDOWN:
                    IsLeftMouseDown = true;
                    break;
                case WM_LBUTTONUP:
                    IsLeftMouseDown = false;
                    break;
            }
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }
}

使用方式

// 程序启动时注册钩子
MouseHook.SetHook();

// 循环中直接读取状态判断
void loop()
{
    if (MouseHook.IsLeftMouseDown)
    {
        LeftClick();
    }
}

// 程序退出时卸载钩子
MouseHook.Unhook();

二、解决模拟点击干扰状态检测的问题

问题根源

mouse_event是老旧API,模拟的鼠标输入会被系统当作真实硬件输入,发送WM_LBUTTONUP消息,导致GetAsyncKeyState误判左键状态。

解决方案:改用SendInput API

SendInput是Windows推荐的现代输入模拟API,可区分模拟输入与物理输入,不会干扰物理按键的状态检测。

替换后的LeftClick方法

[StructLayout(LayoutKind.Sequential)]
struct INPUT
{
    public int type;
    public MOUSEINPUT mi;
}

[StructLayout(LayoutKind.Sequential)]
struct MOUSEINPUT
{
    public int dx;
    public int dy;
    public uint mouseData;
    public uint dwFlags;
    public uint time;
    public IntPtr dwExtraInfo;
}

[DllImport("user32.dll", SetLastError = true)]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

public void LeftClick()
{
    INPUT[] inputs = new INPUT[2];

    // 模拟左键按下
    inputs[0].type = 0; // 鼠标输入类型
    inputs[0].mi.dwFlags = (uint)MouseEventFlags.MOUSEEVENTF_LEFTDOWN;

    // 模拟左键抬起
    inputs[1].type = 0;
    inputs[1].mi.dwFlags = (uint)MouseEventFlags.MOUSEEVENTF_LEFTUP;

    SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT)));
}

完整优化代码示例

using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace MouseAutoClickDemo
{
    public class MouseHook
    {
        private const int WH_MOUSE_LL = 14;
        private const int WM_LBUTTONDOWN = 0x0201;
        private const int WM_LBUTTONUP = 0x0202;

        private static LowLevelMouseProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;
        public static bool IsLeftMouseDown { get; private set; } = false;

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

        [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);

        public static void SetHook()
        {
            using (var curProcess = System.Diagnostics.Process.GetCurrentProcess())
            using (var curModule = curProcess.MainModule)
            {
                _hookID = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }

        public static void Unhook()
        {
            UnhookWindowsHookEx(_hookID);
        }

        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                switch ((int)wParam)
                {
                    case WM_LBUTTONDOWN:
                        IsLeftMouseDown = true;
                        break;
                    case WM_LBUTTONUP:
                        IsLeftMouseDown = false;
                        break;
                }
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
    }

    [Flags]
    enum MouseEventFlags : uint
    {
        MOUSEEVENTF_MOVE = 0x0001,
        MOUSEEVENTF_LEFTDOWN = 0x0002,
        MOUSEEVENTF_LEFTUP = 0x0004,
        MOUSEEVENTF_RIGHTDOWN = 0x0008,
        MOUSEEVENTF_RIGHTUP = 0x0010,
        MOUSEEVENTF_MIDDLEDOWN = 0x0020,
        MOUSEEVENTF_MIDDLEUP = 0x0040,
        MOUSEEVENTF_XDOWN = 0x0080,
        MOUSEEVENTF_XUP = 0x0100,
        MOUSEEVENTF_WHEEL = 0x0800,
        MOUSEEVENTF_VIRTUALDESK = 0x4000,
        MOUSEEVENTF_ABSOLUTE = 0x8000
    }

    [StructLayout(LayoutKind.Sequential)]
    struct INPUT
    {
        public int type;
        public MOUSEINPUT mi;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct MOUSEINPUT
    {
        public int dx;
        public int dy;
        public uint mouseData;
        public uint dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    public class MouseController
    {
        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);

        public void LeftClick()
        {
            INPUT[] inputs = new INPUT[2];

            inputs[0].type = 0;
            inputs[0].mi.dwFlags = (uint)MouseEventFlags.MOUSEEVENTF_LEFTDOWN;

            inputs[1].type = 0;
            inputs[1].mi.dwFlags = (uint)MouseEventFlags.MOUSEEVENTF_LEFTUP;

            SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT)));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MouseHook.SetHook();
            var controller = new MouseController();

            Console.WriteLine("按住鼠标左键启动自动点击,按任意键退出");

            while (!Console.KeyAvailable)
            {
                if (MouseHook.IsLeftMouseDown)
                {
                    controller.LeftClick();
                    Thread.Sleep(50); // 控制点击间隔,避免操作过于频繁
                }
            }

            MouseHook.Unhook();
        }
    }
}

核心说明

  • 全局鼠标钩子直接捕获系统层面的鼠标消息,精准记录物理左键的真实状态,完全不受模拟输入影响。
  • SendInput替代mouse_event后,模拟的输入不会篡改系统对物理按键状态的判断,同时兼容性更强。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 23:10:59