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

如何在Delphi中使用SendInput向DirectX9游戏发送键盘输入?

用SendInput给DirectX9游戏发送键盘输入的问题解答

一、验证SendInput的Delphi代码转换正确性

正确的Delphi实现需要严格对齐Windows API的结构体定义和函数调用,以下是标准实现:

type
  TInputType = (INPUT_MOUSE = 0, INPUT_KEYBOARD = 1, INPUT_HARDWARE = 2);
  TKeybdFlag = (KEYEVENTF_EXTENDEDKEY = $0001, KEYEVENTF_KEYUP = $0002,
                KEYEVENTF_SCANCODE = $0008, KEYEVENTF_UNICODE = $0004);
  TKeybdFlags = set of TKeybdFlag;

  tagINPUT = record
    type_: DWORD;
    case Integer of
      0: (mi: TMouseInput);
      1: (ki: TKeybdInput);
      2: (hi: THardwareInput);
  end;
  TINPUT = tagINPUT;

  tagKEYBDINPUT = record
    wVk: WORD;
    wScan: WORD;
    dwFlags: DWORD;
    time: DWORD;
    dwExtraInfo: ULONG_PTR;
  end;
  TKEYBDINPUT = tagKEYBDINPUT;

function SendInput(cInputs: UINT; pInputs: PINPUT; cbSize: Integer): UINT; stdcall; external 'user32.dll';

// 发送按键按下(虚拟键模式)
procedure SendKeyDown(const VKCode: Word);
var
  Input: TINPUT;
begin
  ZeroMemory(@Input, SizeOf(TINPUT));
  Input.type_ := Integer(INPUT_KEYBOARD);
  Input.ki.wVk := VKCode;
  SendInput(1, @Input, SizeOf(TINPUT));
end;

// 发送按键释放(虚拟键模式)
procedure SendKeyUp(const VKCode: Word);
var
  Input: TINPUT;
begin
  ZeroMemory(@Input, SizeOf(TINPUT));
  Input.type_ := Integer(INPUT_KEYBOARD);
  Input.ki.wVk := VKCode;
  Input.ki.dwFlags := Integer(KEYEVENTF_KEYUP);
  SendInput(1, @Input, SizeOf(TINPUT));
end;

如果你的转换代码和上述结构一致,基本就是正确的。核心要点是结构体的内存布局要和Windows API完全匹配,函数调用的参数类型不能错。

二、DIKEYBOARD常量替代与=、INS按键发送

DirectInput的DIKEYBOARD_XXX常量本质是硬件扫描码,和Windows原生扫描码一一对应,无需专门的DI常量,直接用以下方式处理:

1. INS按键

  • 虚拟键码:VK_INSERT(值为$2D),Delphi默认定义了该常量,若缺失可手动声明:const VK_INSERT = $2D;
  • 扫描码:$52(十进制82),如果游戏只识别扫描码,用扫描码模式发送即可。

2. =按键(主键盘)

  • 虚拟键码:VK_OEM_PLUS(值为$BB)
  • 扫描码:$3D(十进制61)

扫描码模式发送示例

如果游戏不响应虚拟键,改用扫描码模式:

procedure SendScanCodeDown(const ScanCode: Word);
var
  Input: TINPUT;
begin
  ZeroMemory(@Input, SizeOf(TINPUT));
  Input.type_ := Integer(INPUT_KEYBOARD);
  Input.ki.wScan := ScanCode;
  Input.ki.dwFlags := Integer(KEYEVENTF_SCANCODE);
  SendInput(1, @Input, SizeOf(TINPUT));
end;

procedure SendScanCodeUp(const ScanCode: Word);
var
  Input: TINPUT;
begin
  ZeroMemory(@Input, SizeOf(TINPUT));
  Input.type_ := Integer(INPUT_KEYBOARD);
  Input.ki.wScan := ScanCode;
  Input.ki.dwFlags := Integer(KEYEVENTF_SCANCODE or KEYEVENTF_KEYUP);
  SendInput(1, @Input, SizeOf(TINPUT));
end;

// 发送INS按键
SendScanCodeDown($52);
Sleep(50); // 模拟按键按下时长,可调整
SendScanCodeUp($52);

// 发送=按键
SendScanCodeDown($3D);
Sleep(50);
SendScanCodeUp($3D);

三、游戏未接收输入的排查与解决

1. 窗口激活的正确方式

MakeWindowActive可能无法确保游戏窗口获得焦点,改用Windows原生API:

procedure ActivateGameWindow(const HWnd: HWND);
begin
  SetForegroundWindow(HWnd);
  SetFocus(HWnd);
  // 可选:强制窗口置顶(部分游戏可能需要)
  SetWindowPos(HWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE);
end;

2. AttachThreadInput的正确使用

当游戏程序的输入队列与你的程序线程隔离时,调用AttachThreadInput可以让输入事件被游戏接收:

procedure SendInputToGame(const HWnd: HWND; const Input: TINPUT);
var
  GameThreadId: DWORD;
  CurrentThreadId: DWORD;
begin
  GameThreadId := GetWindowThreadProcessId(HWnd, nil);
  CurrentThreadId := GetCurrentThreadId;
  
  // 关联线程输入队列
  if AttachThreadInput(CurrentThreadId, GameThreadId, True) then
  try
    ActivateGameWindow(HWnd);
    SendInput(1, @Input, SizeOf(TINPUT));
  finally
    // 解除关联
    AttachThreadInput(CurrentThreadId, GameThreadId, False);
  end;
end;

3. 其他注意事项

  • 部分DirectX游戏会检测输入来源,可能需要模拟更真实的输入(比如添加合理的Sleep间隔,避免连续发送太快)。
  • 确保你的程序有足够权限(比如以管理员身份运行,避免UAC拦截)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:24:52