发送按键的选项
- 如果窗口必须聚焦才能发送按键,请使用
SendInput 而不是PostMessage,因为它是为这个过程设计的。这种情况在游戏中很常见,如果游戏窗口没有获得焦点,它们会自动暂停,因此发送任何按键都可能无济于事。
- 如果您希望在发送按键时窗口焦点是可选的,那么
PostMessage 似乎是唯一的方法。
要“模拟”按键,发送虚拟键代码(键“A”)和扫描代码(键盘上的位置“A”)和一些附加标志 Keystroke Message Flags。
PostMessage(hWndChild, WM_KEYDOWN, virtualKey, scanCode << 16 | 0x0000001);
PostMessage(hWndChild, WM_KEYUP, virtualKey, scanCode << 16 | 0xC0000001);
您可以使用 MapVirtualKey 在 virtualKeys 和 scanCodes 之间进行映射
完整程序示例
class Program
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, uint Msg, uint wParam, uint lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
const uint WM_KEYDOWN = 0x0100;
const uint WM_KEYUP = 0x0101;
const uint MAPVK_VK_TO_VSC = 0x00;
// virtual key codes
const uint VK_ESCAPE = 0x1B;
const uint VK_LSHIFT = 0xA0;
const uint VK_LCONTROL = 0xA2;
const uint VK_OEM_2 = 0xBF; // For the US standard keyboard, the '/?' key
const uint VK_W = 'W';
const uint VK_A = 'A';
const uint VK_S = 'S';
const uint VK_D = 'D';
// Scan Code for US keyboard layout
const uint VSC_W = 0x11; // MapVirtualKey('W', MAPVK_VK_TO_VSC);
const uint VSC_A = 0x1E; // On QUERY MapVirtualKey('A', MAPVK_VK_TO_VSC);
// On AZERY MapVirtualKey('Q', MAPVK_VK_TO_VSC);
const uint VSC_S = 0x1F;
const uint VSC_D = 0x20;
const uint VSC_LSHIFT = 0x2A;
// Write 'w' (yes lower case) in Notepad.exe (*Untitled - Notepad)
static void Example1()
{
uint keyPress = VK_W;
uint scanCode = MapVirtualKey(keyPress, MAPVK_VK_TO_VSC);
IntPtr hWnd = FindWindow(null, "*Untitled - Notepad");
IntPtr hWndChild = FindWindowEx(hWnd, IntPtr.Zero, "Edit", string.Empty);
PostMessage(hWndChild, WM_KEYDOWN, keyPress, scanCode << 16 | 0x0000001);
Thread.Sleep(42);
PostMessage(hWndChild, WM_KEYUP, keyPress, scanCode << 16 | 0xC0000001);
}
// Write 'help' (yes lower case) in Notepad.exe (*Untitled - Notepad)
static void Example2()
{
string text = "help";
IntPtr hWnd = FindWindow(null, "*Untitled - Notepad");
IntPtr hWndChild = FindWindowEx(hWnd, IntPtr.Zero, "Edit", string.Empty);
foreach (var c in text)
{
uint keyPress = char.ToUpper(c);
uint scanCode = MapVirtualKey(keyPress, MAPVK_VK_TO_VSC);
PostMessage(hWndChild, WM_KEYDOWN, keyPress, scanCode << 16 | 0x0000001);
Thread.Sleep(42);
PostMessage(hWndChild, WM_KEYUP, keyPress, scanCode << 16 | 0xC0000001);
Thread.Sleep(142);
}
}
}
一些额外的背景阅读
https://handmade.network/forums/t/2011-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names
到达解决方案
Visual Studio 自带一个叫做 Spy++ 的程序,可以用来查看任何窗口的消息队列。
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\spyxx.exe
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\spyxx_amd64.exe
spyxx.exe 用于检查 32 位进程,spyxx_amd64.exe 用于检查 64 位进程。使用 Spy++ 和文档 Keystroke Message Flags,我能够查看 WM_KEYDOWN 和 WM_KEYUP 并使用 PostMessage 模仿所需的行为。
潜在的空头下跌
- 您可以发送一个 Shift 键,大多数游戏都应该收到该 Shift 键,但文本框中的 [Shift]+[A] 等操作会产生“a”。 Windows 使用其他一些机制来生成大写字母。
- 某些程序可能会使用
GetKeyboardState 它在这种情况下发布消息不会做任何事情,因为它会查询键盘并忽略消息。请注意,有一个 SetKeyboardState 可用于设置按下哪些键,从而与这些程序进行通信。