SendInput是当前唯一靠谱的选择,因其走系统输入队列,支持DPI缩放、多显示器映射及UIPI兼容,而mouse_event已被微软废弃,存在坐标偏移、静默失败等风险。

直接用 SendInput,别碰 mouse_event——除非你维护的是十年前的 .NET Framework 项目,且明确知道它在高 DPI、多显示器、UAC 提权窗口下必然点偏或静默失败。
为什么 SendInput 是当前唯一靠谱的选择
微软早在 Windows Vista 就标记 mouse_event 为废弃,不是因为它不能用,而是它绕过了现代输入栈:不感知 DPI 缩放、不区分多显示器虚拟屏幕、被 UIPI(用户界面特权隔离)拦截时也不报错。而 SendInput 是当前输入模型的官方入口,事件走系统输入队列,能正确映射到目标桌面会话和缩放上下文。
-
SendInput的dx/dy值单位是 0–65535 的归一化坐标,不是像素——必须自己算:(int)(x * 65535.0 / Screen.PrimaryScreen.Bounds.Width) - 结构体声明必须带
[StructLayout(LayoutKind.Sequential)],且dwType必须设为0(即INPUT_MOUSE),填错就静默失败 - 调用后检查返回值:
if (SendInput(1, ref input, INPUT.Size) == 0),再查Marshal.GetLastWin32Error(),常见错误码 5(拒绝访问)说明被 UIPI 拦截,1400(无效窗口句柄)说明目标已销毁
mouse_event 还能用吗?能,但得踩够所有坑
如果你非得用 mouse_event(比如对接旧测试框架),那必须满足三个硬条件,缺一不可:
- 传入的
dwFlags类型必须是uint,不是int——否则MOUSEEVENTF_ABSOLUTE(0x8000)会被符号扩展成负数,标志位失效 - 坐标必须归一化:
dx = (int)(x * 65535.0 / screen.Width),dy = (int)(y * 65535.0 / screen.Height);漏掉这步,永远点在左上角 - 不能在 WPF/WinForms UI 线程里直接调用——容易被线程挂起或触发安全拦截;包一层
Task.Run(() => mouse_event(...))更稳
点击没反应?先确认是不是“根本没资格发”
不是代码写错了,而是 Windows 输入模型根本不让你发:默认情况下,SendInput 和 mouse_event 只能向「当前活动桌面」和「前台进程」投递事件。远程桌面、锁屏、最小化窗口、管理员权限窗口(如 UAC 弹窗)全都会丢弃输入。
- 用
GetForegroundWindow()看目标窗口是否真在前台;用GetThreadDesktop(GetCurrentThreadId())判断当前线程是否在交互式桌面 -
SetForegroundWindow在 Win10+ 上受限极严,跨会话(如服务进程)调用必失败 - 真正绕过限制的方式是注入目标进程并用
PostMessage发送WM_LBUTTONDOWN——但这需要你拿到目标窗口句柄,且消息参数要按 Win32 规范组装:lParam = x | (y
别忘了最简单却最容易被忽略的路
如果你只是想让某个按钮“被点一下”,而不是模拟物理鼠标动作,那就完全不需要 API:
- WinForms:
button1.PerformClick()或直接调button1_Click(null, null) - WPF:
button1.RaiseEvent(new RoutedEventArgs(Button.ClickEvent))
这些方式不依赖焦点、不关心 DPI、不触发 UIPI 检查,适合单元测试和逻辑层自动化——但只对本进程内控件有效。
归一化坐标的计算、结构体内存对齐、UIPI 权限边界、桌面会话隔离……这些不是细节,是决定模拟点击能否落地的硬门槛。写两行 SendInput 很容易,让它在客户机器上稳定运行,才是真正的难点。


















