SendInput是Windows平台下C#实现跨进程鼠标控制的唯一可靠方案,需构造INPUT结构、使用绝对坐标映射(0–65535)、激活目标窗口并处理DPI缩放与多屏偏移。

Windows 平台下,C# 本身不提供跨进程的鼠标控制能力,System.Windows.Forms.Cursor 只能改本地窗体光标位置,对桌面其他程序无效;真要模拟全局鼠标移动和点击,必须调用 Windows API 的 SendInput —— 这是唯一可靠、兼容性好、且不被现代 Windows(含 Win10/11)拦截的方式。
为什么不能用 Cursor.Position + mouse_event?
Cursor.Position 设置的是当前线程 UI 上下文中的逻辑坐标,仅影响本进程窗体,对外部窗口完全无感;而早已废弃的 mouse_event 在 Vista 之后默认被 UIPI(User Interface Privilege Isolation)拦截,普通权限进程调用会静默失败——你看到鼠标没动、没点,却没有任何异常抛出,极易误判为代码没生效。
实际开发中常见错误现象:
- 调用
Cursor.Position = new Point(100, 100)后,目标程序窗口毫无反应 - 用
mouse_event发送左键按下,但远程窗口不响应,且调试器里看不出报错 - 程序以管理员权限运行后“突然能用了”,但这不是解法——它破坏最小权限原则,还可能触发 UAC 弹窗打断自动化流程
用 SendInput 实现安全可靠的鼠标操作
SendInput 是 Windows 提供的低层输入模拟接口,绕过 UIPI 限制,只要你的进程有桌面交互权限(绝大多数前台运行的 GUI 程序都默认具备),就能生效。关键在于构造正确的 INPUT 结构体并调用 P/Invoke。
实操要点:
- 必须用
[DllImport("user32.dll")]导入SendInput,参数类型严格匹配:输入数量(uint)、输入数组指针(INPUT*)、结构体大小(sizeof(INPUT)) - 鼠标移动需设
dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,且坐标范围映射到 0–65535(即全屏归一化值),换算公式:(x * 65535) / Screen.PrimaryScreen.Bounds.Width - 点击动作要分两步:先
MOUSEEVENTF_LEFTDOWN,再MOUSEEVENTF_LEFTUP,中间建议加Thread.Sleep(10)避免被识别为抖动 - 不要省略
MOUSEEVENTF_ABSOLUTE标志——缺了它,dx/dy就变成相对位移,极易失控
示例片段(简化版):
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
public uint type;
public MouseInput mi;
}
<p>[StructLayout(LayoutKind.Sequential)]
public struct MouseInput
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}</p><p>const uint MOUSEEVENTF_MOVE = 0x0001;
const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
const uint MOUSEEVENTF_LEFTUP = 0x0004;</p><p>[DllImport("user32.dll")]
static extern uint SendInput(uint nInputs, INPUT* pInputs, int cbSize);
自动化时容易被忽略的权限与时机问题
即使 SendInput 调用成功,也常因两个隐形条件失败:
- 目标窗口未激活或被遮挡:Windows 不向后台窗口投递鼠标事件(哪怕你坐标算得准),必须先用
SetForegroundWindow激活目标句柄,且该窗口需处于可交互状态(非禁用、非最小化) - 高 DPI 缩放干扰:如果程序未声明 DPI 感知(
SetProcessDpiAwarenessContext或 app.manifest 配置),Screen.PrimaryScreen.Bounds返回的是缩放后逻辑尺寸,但SendInput要的是物理像素归一化值,会导致点击偏移 - 多显示器场景下,
GetSystemMetrics(SM_CXVIRTUALSCREEN)才是真正的虚拟屏幕宽度,别只依赖主屏尺寸
最稳妥的做法是:先用 FindWindow 获取目标窗口句柄 → IsIconic 检查是否最小化 → ShowWindow 还原 → SetForegroundWindow 激活 → 再计算并发送鼠标输入。
真正难的从来不是怎么发指令,而是确保指令发到了正确上下文里——坐标算错可以调,权限不对能加,但窗口焦点、DPI 模式、多屏偏移这些,往往要等自动化跑崩了才回头翻文档。


















