模拟按键 - keybd_event Send/PostMessage的一些思考
本文最后更新于:2022年3月23日 下午
前情提要
在软件开发,特别是辅助软件开发过程中,经常需要用到键盘按键模拟来实现自动化
比如,使用虚拟键码VK_VOLUME_UP
& VK_VOLUME_DOWN
来实现音量调节
或者,使用VK_SNAPSHOT
实现截屏
这是一种可以避开系统API
复杂操作的,非常讨巧的捷径
常用函数
keybd_event
为了实现按键模拟,我们常用Windows API
- keybd_event
:
1 |
|
一般情况下,我们只需要使用bVK
& dwFlags
参数即可(复杂情况,如游戏窗口,需要加上扫描码)
一个击键的过程分为两个步骤:按下 & 抬起
所以,如果我们要模拟键盘上A
键的击键:
1 |
|
或者是,回车键(ENTER)
:
1 |
|
知道了这点,那么模拟组合键也不在话下了,如Ctrl+Tab
:
1 |
|
其他按键的宏定义可以参考:虚拟键代码(Virtual-Key Codes)
注:微软官方建议 - 此功能已被取代。请改用SendInput(其实差不多)
SendMessage/PostMessage
众所周知,Windows
系统是基于消息的,而这两个函数都是向窗口发送消息
他们的区别在于:
SendMessage
是同步函数,或者说阻塞函数,调用指定窗口的窗口过程,直到窗口过程处理完消息才返回PostMessage
是异步函数,在与创建指定窗口的线程关联的消息队列中发布一条消息,并在不等待线程处理消息的情况下返回。
也就是说,SendMessage
返回的时候,消息肯定被对方处理完了
而PostMessage
不管处理没处理,什么时候处理,直接扔过去就返回了
好处是,对方不回应的时候,自己不会卡死(挂起)
为什么要提这两个函数呢,因为Windows
基于消息,所以按键也是消息(WM_KEYDOWN | WM_KEYUP)
那么只要向指定窗口发送消息就可以模拟按键了:
1 |
|
比keybd_event
多一个参数,这里的hwnd
指的是窗口句柄,类似于窗口的ID
而PostMessage
如果这么写的话,就会发现不对劲了,按键貌似被重复了两遍
查询资料 & 官方文档可知,我们忽略了最后一个参数:lParam
重复计数、扫描码、扩展键标志、上下文代码、前一个键状态标志和转换状态标志,如下所示
击键消息的lParam参数包含有关生成消息的击键的附加信息。该信息包括重复计数、扫描码、扩展键标志、上下文代码、前一个键状态标志和转换状态标志。下图显示了这些标志和值在lParam参数中的位置。
也就是在一个32
位的参数中,表示了大量标志信息,这需要用到位运算
1 |
|
这样才能完成一次PostMessage
击键消息的正确发送
显然是SendMessage
更方便,但要考虑到阻塞函数的成本
问题与思考
以上都是一些基础操作,没什么好说的,也都是大家公认的
但是呢,需求一旦复杂化,基本的信息就不够用了,问题就蜂拥而至了
差异
首先我们说一下,keybd_event
与 Send/PostMessage
的区别吧
keybd_event
是全局按键模拟,也就是像真实地按键一样,将消息发送给前台活动窗口,也就是焦点窗口,这是一大限制Send/PostMessage
可以自行指定发送的窗口对象,可以向无焦点的窗口发送按键信息,更灵活
如果世上真有这样完美的函数就好了,那另一方就会被抹杀
这两者同时存在是有原因的:
Send/PostMessage
无法模拟组合键(ALT
除外),这是一个很大的限制,因为消息只是状态的切换,不能维持按下,而程序通常是直接检测Ctrl Shift
等修饰键的状态来决定组合键的- 模拟按键时,
SendMessage
有时会失效(如QQ窗口),所以建议用PostMessage
(原因不明)
组合键处理
那怎么办,要发送组合键怎么办
如果是活动窗口,那直接keybd_event
模拟,没啥好说的
如果是非活动窗口怎么办
- 最粗暴,直接将其变为前台活动窗口,然后再模拟(设置前台窗口参见:AttachThreadInput & SetForegroundWindow)
- ↑但这是个悖论,这样就不是非活动窗口了
- 或者用
keybd_event
模拟修饰键(如Ctrl
) +Send/PostMessage
模拟其他按键(如A
),但问题在于无法很好地配合消息顺序,因为keybd_event
不会等待消息处理 - 解决办法:在
keybd_event
之后延时一段时间,再进行SendMessage
,可实现对非活动窗口的组合键控制
往下 再往下(驱动级模拟)
一般的窗口这样就行了,但是游戏窗口可能会直接使用底层硬件输入(低延时),而不经过Windows
这时候就不能在使用Windows
消息机制了,必须直接对底层端口进行操作
这时候就需要外力帮助了
WinIO程序库允许在32位的Windows应用程序中直接对I/O端口和物理内存进行存取操作。通过使用一种内核模式的设备驱动器和其它几种底层编程技巧,它绕过了Windows系统的保护机制
拓展资料:
Ref
keybd_event function (winuser.h) - Win32 apps | Microsoft Docs
About Keyboard Input - Win32 apps | Microsoft Docs
WM_KEYDOWN message (Winuser.h) - Win32 apps | Microsoft Docs
通过PostMessage/SendMessage实现模拟键盘鼠标按键,发送不成功或出现重复按键的可参考本文_lzl_li的博客-CSDN博客_c# sendmessage模拟键盘