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

C# Windows窗体宏录制器:鼠标模拟点击失效问题求助

解决C#宏录制器管理员权限下回放鼠标点击无效的问题

针对你遇到的「普通权限下部分程序无法录制点击,管理员权限能录制但回放时鼠标到位却无点击响应」的问题,以下是几个可行的解决方向:

1. 绕过UIPI限制,直接向目标窗口发送鼠标消息

当宏录制器以管理员身份运行、目标程序为普通权限时,UIPI(用户界面特权隔离)会拦截全局输入模拟(如SendInput)。此时直接向目标窗口的指定坐标发送WM_LBUTTONDOWN和WM_LBUTTONUP消息是更可靠的方案:

[DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(POINT point);

[DllImport("user32.dll")]
private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int X;
    public int Y;
}

private const uint WM_LBUTTONDOWN = 0x0201;
private const uint WM_LBUTTONUP = 0x0202;
private const int MK_LBUTTON = 0x0001;

// 回放鼠标点击
public void SimulateWindowMouseClick(int screenX, int screenY)
{
    // 获取点击坐标对应的窗口句柄
    POINT screenPoint = new POINT { X = screenX, Y = screenY };
    IntPtr targetWnd = WindowFromPoint(screenPoint);
    
    // 转换为窗口客户区坐标
    ScreenToClient(targetWnd, ref screenPoint);
    
    // 构造消息参数:低16位存X,高16位存Y
    IntPtr lParam = (IntPtr)((screenPoint.Y << 16) | (screenPoint.X & 0xFFFF));
    IntPtr wParam = (IntPtr)MK_LBUTTON;
    
    // 发送按下、抬起消息
    PostMessage(targetWnd, WM_LBUTTONDOWN, wParam, lParam);
    PostMessage(targetWnd, WM_LBUTTONUP, IntPtr.Zero, lParam);
}

2. 修正SendInput的绝对坐标转换逻辑

若坚持使用SendInput,需注意MOUSEEVENTF_ABSOLUTE标志要求坐标为0-65535范围的绝对屏幕坐标(而非像素值),之前的实现可能未完成正确转换:

[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
    public uint Type;
    public MOUSEKEYBDHARDWAREINPUT Data;
}

[StructLayout(LayoutKind.Explicit)]
public struct MOUSEKEYBDHARDWAREINPUT
{
    [FieldOffset(0)]
    public MOUSEINPUT Mouse;
}

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

private const uint INPUT_MOUSE = 0;
private const uint MOUSEEVENTF_MOVE = 0x0001;
private const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
private const uint MOUSEEVENTF_LEFTUP = 0x0004;

public void SendInputMouseClick(int screenX, int screenY)
{
    // 将像素坐标转换为0-65535的绝对范围
    int absoluteX = (screenX * 65535) / Screen.PrimaryScreen.Bounds.Width;
    int absoluteY = (screenY * 65535) / Screen.PrimaryScreen.Bounds.Height;
    
    INPUT[] inputs = new INPUT[3];
    
    // 移动到目标绝对坐标
    inputs[0].Type = INPUT_MOUSE;
    inputs[0].Data.Mouse.dx = absoluteX;
    inputs[0].Data.Mouse.dy = absoluteY;
    inputs[0].Data.Mouse.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
    
    // 左键按下
    inputs[1].Type = INPUT_MOUSE;
    inputs[1].Data.Mouse.dwFlags = MOUSEEVENTF_LEFTDOWN;
    
    // 左键抬起
    inputs[2].Type = INPUT_MOUSE;
    inputs[2].Data.Mouse.dwFlags = MOUSEEVENTF_LEFTUP;
    
    SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
}

3. 激活目标窗口后再模拟输入

部分程序仅响应当前激活窗口的输入事件,回放前可先调用SetForegroundWindow激活目标窗口:

[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);

// 回放前执行:
IntPtr targetWnd = // 可通过录制时记录的窗口句柄获取
SetForegroundWindow(targetWnd);
Thread.Sleep(100); // 延迟确保窗口激活

4. 启用SeDebugPrivilege权限

给进程启用调试权限,提升输入模拟的优先级,可解决部分高权限程序的拦截问题:

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint BufferLength, IntPtr PreviousState, IntPtr ReturnLength);

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);

[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);

[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
    public uint LowPart;
    public int HighPart;
}

[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
    public uint PrivilegeCount;
    public LUID Luid;
    public uint Attributes;
}

private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const uint TOKEN_QUERY = 0x0008;
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;

public bool EnableDebugPrivilege()
{
    if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out IntPtr tokenHandle))
        return false;
    
    TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();
    tp.PrivilegeCount = 1;
    tp.Luid = new LUID();
    tp.Attributes = SE_PRIVILEGE_ENABLED;
    
    if (!LookupPrivilegeValue(null, "SeDebugPrivilege", ref tp.Luid))
        return false;
    
    bool result = AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
    CloseHandle(tokenHandle);
    return result;
}

// 程序启动时调用:
EnableDebugPrivilege();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:18:18