如何在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
相关产品推荐
相关产品推荐

