半生听风吟 2026-02-26 03:00 采纳率: 98.6%
浏览 4
已采纳

SendInput模拟鼠标移动时坐标不生效,原因何在?

SendInput模拟鼠标移动时坐标不生效,常见原因在于:**坐标值未按绝对屏幕坐标归一化处理**。SendInput要求MOUSEEVENTF_ABSOLUTE标志下的dx/dy参数必须是0–65535范围的归一化值(对应全屏宽高),而非像素坐标。若直接传入屏幕像素(如x=100, y=200),系统会将其误判为微小相对位移(约0.15%屏幕宽度),导致“不动”或偏移极小。此外,若未同时设置MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,或遗漏dwExtraInfo、time字段(虽非必须但部分沙箱/安全策略下影响投递),也可能失败。还需注意:多显示器环境下需转换到主屏坐标系;UAC提权程序对桌面隔离(Session 0隔离)可能导致输入被拦截;以及部分远程桌面、游戏反作弊机制会过滤或重写SendInput事件。调试建议:先用GetCursorPos验证目标位置,再按公式`dx = x * 65535 / GetSystemMetrics(SM_CXSCREEN)`换算,并确保调用线程拥有前台权限(AllowSetForegroundWindow)。
  • 写回答

1条回答 默认 最新

  • 高级鱼 2026-02-26 03:00
    关注
    ```html

    一、表层现象:SendInput鼠标移动“看似无响应”

    开发者调用SendInput设置dx=100, dy=200后,光标几乎静止或仅发生像素级抖动——这是最典型的误用表征。根本原因并非API失效,而是Windows输入子系统将未归一化的像素值(如100)直接解释为“相对位移的100个逻辑单位”,而该单位在MOUSEEVENTF_ABSOLUTE模式下被强制映射到0–65535的归一化坐标空间,导致实际位移仅为100 / 65535 ≈ 0.15%屏幕宽度。

    二、核心机制解析:绝对坐标的归一化契约

    • 归一化公式dx = (int)((double)x * 65535.0 / GetSystemMetrics(SM_CXSCREEN))
    • 纵轴同步换算dy = (int)((double)y * 65535.0 / GetSystemMetrics(SM_CYSCREEN))
    • 双标志缺一不可:必须同时启用MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE
    • 精度陷阱:整数截断需四舍五入(round()),避免向下取整累积偏移

    三、环境维度排查清单

    维度风险点验证方法
    多显示器坐标未转换至主屏虚拟屏幕(GetSystemMetrics(SM_XVIRTUALSCREEN)EnumDisplayMonitors获取各屏边界,判断目标点所属Monitor
    UAC/Session隔离高完整性进程运行于Session 0,无法向用户Session(如Session 1)投递输入WTSQuerySessionInformation确认当前Session ID是否为交互式会话
    安全策略EDR/AV拦截SendInput或沙箱禁用dwExtraInfo字段显式设置mi.dwExtraInfo = GetMessageExtraInfo()提升事件可信度

    四、深度调试路径(含代码片段)

    // ✅ 正确归一化+全字段填充示例
    POINT target = {1280, 720};
    POINT current;
    GetCursorPos(¤t); // 验证初始位置
    int dx = (int)round((double)target.x * 65535.0 / GetSystemMetrics(SM_CXSCREEN));
    int dy = (int)round((double)target.y * 65535.0 / GetSystemMetrics(SM_CYSCREEN));
    
    INPUT input = {0};
    input.type = INPUT_MOUSE;
    input.mi.dx = dx;
    input.mi.dy = dy;
    input.mi.mouseData = 0;
    input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
    input.mi.time = 0; // 或 GetTickCount()
    input.mi.dwExtraInfo = GetMessageExtraInfo(); // 关键防御字段
    
    UINT result = SendInput(1, &input, sizeof(INPUT));
    if (result != 1) {
        DWORD err = GetLastError(); // 检查ERROR_ACCESS_DENIED等
    }
    

    五、反作弊与远程桌面兼容性图谱

    graph LR A[SendInput调用] --> B{目标环境} B -->|本地标准桌面| C[高成功率] B -->|远程桌面RDP| D[需启用“重定向本地资源”且服务端允许UI Automation] B -->|EAC/BattlEye| E[99%概率被过滤
    → 改用UI Automation或低级驱动] B -->|Windows Sandbox| F[默认禁用输入模拟
    → 需组策略配置“AllowInputInjection”]

    六、进阶实践建议

    1. 始终用GetCursorPos + ScreenToClient(若目标为特定窗口)双重校验坐标语义
    2. 对UAC敏感场景,采用PostMessage(WM_MOUSEMOVE)作为降级方案(仅限同进程窗口)
    3. 在自动化测试框架中封装SafeSendAbsoluteMouse函数,内置Session检查、DPI缩放补偿(GetDpiForSystem())、多屏坐标归一化
    4. 记录GetMessageExtraInfo()返回值变化趋势,识别输入事件是否被中间层劫持
    5. 针对游戏场景,预研SetThreadExecutionState防止系统休眠中断输入流

    七、历史演进视角

    从Windows XP时代起,SendInput的归一化设计即为兼容多DPI、多显示器及未来硬件抽象而设。但2012年后UAC强化、2016年Windows Defender Exploit Guard引入输入模拟防护、2020年Windows Sandbox默认隔离策略,使得原本“可用”的代码在新环境中失效——这印证了底层API契约稳定性与上层安全策略演进间的张力关系。资深工程师需建立“API语义—系统策略—硬件抽象”三层认知模型。

    ```
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 2月27日
  • 创建了问题 2月26日