如何向VirtuaNES、ePSXe等游戏应用正确发送Win32窗口消息
问题背景
- 已提前获取目标窗口句柄
hwnd,可基于该句柄拿到对应线程ID、进程ID,也可通过线程ID获取窗口关联的cbSize、flags、hwndActive、hwndFocus、hwndCapture、hwndMenuOwner、hwndMoveSize、hwndCaret、rcCaret等详细信息。 - 两类常规窗口的消息发送方案已验证可正常运行:
- 向记事本发送消息:需要先枚举记事本主窗口下类名为
EDIT的子窗口句柄,再向子句柄发送消息,实现代码如下:
- 向记事本发送消息:需要先枚举记事本主窗口下类名为
win32gui.EnumChildWindows(notepadHwnd, callback, hwnds) # callback用于收集枚举得到的hwnd # hwnds为存储"child hwnd类名":hwnd键值对的对象 win32api.SendMessage(hwnds["EDIT"], win32con.WM_CHAR, ord('1'), 0)
- 向Chrome发送消息:可直接向Chrome主窗口句柄发送按键消息,实现代码如下:
win32api.SendMessage(chromeHwnd, win32con.WM_KEYDOWN, 0x31, 0) time.sleep(0.05) win32api.SendMessage(chromeHwnd, win32con.WM_KEYUP, 0x31, 0)
问题描述
需要向运行在VirtuaNES上的《超级马里奥》、运行在ePSXe上的《最终幻想》这类模拟器游戏窗口发送按键消息,先后尝试三种发送方式均未生效:
- 直接向VirtuaNES主窗口句柄发送按键消息
- 向线程关联的
hwndFocus焦点窗口句柄发送按键消息 - 通过
PostThreadMessage直接向窗口所属线程发送按键消息
对应尝试代码如下:
# 向VirtuaNES主Hwnd发送消息 win32api.SendMessage(nesHwnd, win32con.WM_KEYDOWN, 0x31, 0) time.sleep(0.05) win32api.SendMessage(nesHwnd, win32con.WM_KEYUP, 0x31, 0) # 向VirtuaNES hwndFocus发送消息 win32api.SendMessage(hwndFocus, win32con.WM_KEYDOWN, 0x31, 0) time.sleep(0.05) win32api.SendMessage(hwndFocus, win32con.WM_KEYUP, 0x31, 0) # 向VirtuaNES thread id发送消息 win32api.PostThreadMessage(threadId, win32con.WM_KEYDOWN, 0x31, 0) time.sleep(0.05) win32api.PostThreadMessage(threadId, win32con.WM_KEYUP, 0x31, 0)
原因说明
SendMessage/PostMessage发送键盘消息失效的核心原因是这类模拟器程序不通过Windows窗口消息队列读取键盘输入:
- 记事本、Chrome这类基于标准Win32或通用UI框架开发的程序,输入事件由系统转换为
WM_KEYDOWN/WM_KEYUP/WM_CHAR类型消息投递到窗口消息队列,程序从队列拉取消息响应输入,因此直接发送对应消息可生效。 - VirtuaNES、ePSXe这类模拟器为了降低输入延迟,普遍采用
DirectInput、Raw Input原始输入甚至直接读取键盘硬件状态的方式获取输入,不会处理窗口消息队列中的模拟按键消息,因此常规发消息的方式完全无效。
可行解决方案
方案按通用性、实现成本从高到低排序:
- 首选:
SendInput模拟全局键盘输入
这是微软官方推荐的标准输入模拟接口,生成的输入事件和真实硬件输入的处理路径完全一致,无论程序采用哪种输入读取方式都能识别。使用前需要先将目标模拟器窗口切到前台激活,再调用接口发送对应按键即可。Python环境下可直接使用pywin32库封装的win32api.SendInput接口,也可使用pynput库简化调用逻辑。
注意:SendInput生成的是全局输入事件,模拟输入期间不要手动操作键盘鼠标,避免输入错乱。 - 次选:内存写入模拟按键状态
针对特定版本的模拟器,可逆向定位到程序存储按键按下状态的内存地址,通过WriteProcessMemory接口直接修改对应内存值实现按键模拟。这种方式不需要窗口保持前台激活,也不会干扰全局输入,但需要针对每个模拟器的不同版本单独寻找内存偏移,通用性差。 - 特殊场景:驱动级输入模拟
通过内核驱动模拟硬件端口的输入信号,兼容性最强但开发成本极高,普通自动化场景完全没有必要采用。
注意事项
- 不要使用已被微软标记为废弃的
keybd_event接口,该接口在新版本Windows系统上存在行为不稳定的问题。 - 激活目标窗口时,如果窗口处于最小化状态,需要先调用
ShowWindow恢复窗口显示,再调用SetForegroundWindow将窗口切到前台,否则SendInput的输入会发送到当前激活的其他窗口。 - 部分模拟器自带后台输入开关,开启后可支持非激活状态下的输入识别,但该能力属于模拟器自身的配置项,不是通用方案。
内容的提问来源于stack exchange,提问作者Ted
相关产品推荐
相关产品推荐

