应使用 mouse_event 配合 MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP 标志,并将屏幕坐标归一化为 0–65535 范围;需声明 dwFlags 为 uint,结构体加 [StructLayout(Sequential)],并注意 DPI 缩放影响。

怎么用 mouse_event 实现指定坐标的鼠标点击(C#)
Windows API 的 mouse_event 已废弃,但仍在 Win32 平台广泛可用;它不依赖前台窗口,能跨进程触发点击,适合自动化脚本。不过它操作的是**屏幕绝对坐标**,且需手动切换坐标系(默认是相对移动,要点击得先设为绝对模式)。
常见错误现象:mouse_event 调用后没反应,或点在了错位置——大概率是忘了加 MOUSEEVENTF_ABSOLUTE 标志,或没把像素坐标缩放到 0–65535 范围。
- 必须传入
MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP组合,缺一不可 - 坐标要归一化:
(x * 65535 / screen_width)和(y * 65535 / screen_height),否则永远点在左上角 - 调用前建议用
SetThreadExecutionState防系统休眠,尤其后台运行时 - .NET 6+ 项目默认启用了“仅限 Windows”平台限制,若目标框架是
net6.0而非net6.0-windows,P/Invoke 会编译失败
SendInput 替代方案更稳定,但要注意输入设备上下文
SendInput 是微软推荐的现代替代接口,行为更可靠、支持多点触控和键盘组合,但它的输入事件会进入系统输入队列,受 UIPI(用户界面特权隔离)限制:普通权限进程无法向更高权限窗口(如管理员运行的记事本)发送输入。
使用场景:需要模拟真实用户输入链路(比如连按 + 键盘修饰)、或未来可能迁移到高 DPI/多显示器环境。
- 构造
INPUT结构体时,dwType必须是INPUT_MOUSE,且dx/dy字段含义与mouse_event不同:同样需归一化,且单位仍是 0–65535 - 若只点一次,必须发两个事件:
MOUSEEVENTF_LEFTDOWN+MOUSEEVENTF_LEFTUP,不能合并 - 调用后无返回值,失败时
SendInput返回 0,应检查Marshal.GetLastWin32Error() - 在远程桌面或锁屏状态下,
SendInput可能静默失败(事件被丢弃),而mouse_event同样受限,没有银弹
C# P/Invoke 声明里最容易漏掉的三个细节
写错 P/Invoke 签名是调用失败最常见原因,不是逻辑问题,而是声明失配。
-
mouse_event的dwFlags参数类型必须是uint,不是int—— 否则高位标志(如MOUSEEVENTF_ABSOLUTE= 0x8000)会被符号扩展成负数 -
SendInput的第三个参数cbSize必须传Marshal.SizeOf<INPUT>(),硬编码28或40在 x64/x86 混合环境下必崩 - 所有结构体(如
MOUSEINPUT)必须加[StructLayout(LayoutKind.Sequential)],否则字段内存布局错乱,坐标值读成垃圾数据
高 DPI 和多显示器下坐标计算容易错在哪
Windows 10/11 默认开启 DPI 缩放,GetSystemMetrics(SM_CXSCREEN) 返回的是虚拟屏幕宽,不是物理像素;直接用 Screen.PrimaryScreen.Bounds.Width 也会被缩放干扰。
正确做法是用 GetDpiForWindow + GetSystemMetricsForDpi 获取当前显示区域的真实像素尺寸,但多数简单脚本其实只需绕过缩放:
- 用
Graphics.FromHwnd(IntPtr.Zero).DpiX获取主屏 DPI,再反推原始像素坐标 - 更稳妥:调用
SetThreadDpiAwarenessContext(Windows 10 1703+)设为DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,然后用GetMonitorInfo查每个屏的rcMonitor - 如果只是点固定 UI 元素,优先用
FindWindow+ClientToScreen把控件客户区坐标转屏幕坐标,比纯像素定位鲁棒得多
实际项目里,90% 的坐标偏差问题出在没处理 DPI 缩放,而不是 API 本身难用。










