keybd_event 按键事件
keybd_event 函数 (winuser.h) - Win32 apps | Microsoft Learn
/// <summary>
/// 键盘事件
/// https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-keybd_event
/// </summary>
/// <param name="bVk">
/// 按键的十进制虚拟键值
/// https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
/// </param>
/// <param name="bScan">十进制扫描码,一般模拟键盘不用设置,用 0 代替就行,当向DX游戏模拟输入则需要</param>
/// <param name="dwFlags">选项标志,如果为按键按下则置 0 即可,如果为按键松开则为2</param>
/// <param name="dwExtraInfo">与键笔划关联的附加值,一般置 0 即可</param>
[DllImport("user32.dll", SetLastError = true)]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
一般情况下只需传虚拟键值和按下/松开状态即可满足,但有些情况如在DX开发的游戏中,还需传入硬件扫描码,否则不起作用。但不同键盘厂家对键盘扫描码可能定义不同,所以并不能通过写好一个虚拟码和扫描码的对应关系列表来实现输入扫描码,而需要通过win32的转换方法mapVirtualKeyA来实时转换。
mapVirtualKeyA 按键码与扫描码互转
mapVirtualKeyA 函数 (winuser.h) - Win32 apps | Microsoft Learn
/// <summary>
/// 键盘虚拟码与扫描码互转
/// </summary>
/// <param name="uCode"></param>
/// <param name="uMapType">
/// 0:uCode 参数是一个虚拟键代码,并转换为扫描代码。 如果它是不区分左键和右键的虚拟键代码,则返回左侧扫描代码。 如果没有转换,则函数返回 0。
/// 1:uCode 参数是一个扫描代码,被转换为不区分左键和右键的虚拟键代码。 如果没有转换,则函数返回 0。
/// 2:uCode 参数是一个虚拟键代码,在返回值的低序字中转换为非移位字符值。 死键 (音调符号) 通过设置返回值的顶位来指示。 如果没有转换,则函数返回 0。
/// 3:uCode 参数是扫描代码,并转换为区分左键和右键的虚拟键代码。 如果没有转换,则函数返回 0。
/// 4:uCode 参数是一个虚拟键代码,并转换为扫描代码。 如果它是不区分左键和右键的虚拟键代码,则返回左侧扫描代码。 如果扫描代码是扩展扫描代码,则返回值的高字节将包含指定扩展扫描代码0xe0或0xe1。 如果没有转换,则函数返回 0。
/// </param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern uint mapVirtualKey(uint uCode, uint uMapType);
示例
一般情况下模拟按下松开Ctrl+V
keybd_event(17, 0, 0, 0);//按下左Ctrl
keybd_event(86, 0, 0, 0);//按下V
Thread.Sleep(100);//可有可无
keybd_event(86, 0, 2, 0);//松开左Ctrl
keybd_event(17, 0, 2, 0);//松开V
对DX游戏模拟按下松开Ctrl+V
keybd_event(17, (byte)mapVirtualKey(17, 0), 0, 0);//按下左Ctrl
keybd_event(86, (byte)mapVirtualKey(86, 0), 0, 0);//按下V
Thread.Sleep(100);//可有可无
keybd_event(86, (byte)mapVirtualKey(86, 0), 2, 0);//松开左Ctrl
keybd_event(17, (byte)mapVirtualKey(17, 0), 2, 0);//松开V
扩展:为什么DX游戏需要扫描码
使用DirectX接口的游戏程序,它们在读取键盘操作时绕过了windows的消息机制,而使用DirectInput.这是因为有些游戏对实时性控制的要求比较高,要求以最快速度响应键盘输入。而windows消息由于是队列形式的,消息在传递时会有不少延迟,有时1秒钟也就传递十几条消息,这个速度达不到游戏的要求。而DirectInput则绕过了windows消息,直接与键盘驱动程序打交道,效率当然提高了不少。因此也就造成,对这样的程序无论用PostMessage或者是keybd_event都不会有反应,因为这些函数都在较高层。对于这样的程序,只好用直接读写键盘端口的方法来模拟硬件事件了。