Windows下使用键盘钩子实现自动化
键盘钩子是Windows操作系统提供的强大机制,允许应用程序监视和处理系统中的键盘输入事件。
通过拦截和分析键盘消息,我们可以实现各种自动化键盘操作。
实战
打印键盘输入
下面是一个最简单的例子,用于记录用户的键盘输入:
- 定义键盘钩子的回调函数
PrintKeyboardInputProc用于打印键盘扫描码、虚拟码和状态;扫描码和虚拟码的区别后文会给出。 - SetWindowsHookEx设置键盘钩子,第一个参数为
WH_KEYBOARD_LL,表示低级键盘钩子,用于捕获所有窗口的键盘事件;
第二个参数即我们实现的回调函数。 while (GetMessage(&msg, NULL, 0, 0)) {}消息循环是必须的,用于捕获windows窗口消息。- 返回
CallNextHookEx(NULL, code, wParam, lParam);让其他钩子程序也能捕获该事件;如果返回任意大于1的数,其他窗口都无法获取键盘输入,这个逻辑可以用于劫持或重写按键的输入。
#include <stdio.h>
#include <windows.h>
static LRESULT PrintKeyboardInputProc(int code, WPARAM wParam, LPARAM lParam) {
KBDLLHOOKSTRUCT* ks = (KBDLLHOOKSTRUCT*)lParam;
printf("scan_code:%lu vk_code:%lu press:%d\n", ks->scanCode, ks->vkCode,
wParam);
return CallNextHookEx(NULL, code, wParam, lParam);
}
int main() {
SetWindowsHookEx(WH_KEYBOARD_LL, PrintKeyboardInputProc, NULL, 0);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
}
}
编译后执行,输入123abc:
打印每一次的扫描码、虚拟码和按键状态值。
press:256表示按键按下,press:257表示按键释放。
键盘输入截获并重写
以下代码实现了按键的的重定向,F7音量减,F8音量加:
- 匹配到
F7、F8时,返回1,这样其他程序就无法获取F7、F8的输入,相当于物理键盘重映射。
#include <stdio.h>
#include <windows.h>
static LRESULT KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
KBDLLHOOKSTRUCT* ks = (KBDLLHOOKSTRUCT*)lParam;
if (ks->vkCode == VK_F7) {
DWORD dwFlags = wParam == WM_KEYDOWN ? 0 : KEYEVENTF_KEYUP;
keybd_event(VK_VOLUME_UP, 0, dwFlags, 0);
return 1;
}
if (ks->vkCode == VK_F8) {
DWORD dwFlags = wParam == WM_KEYDOWN ? 0 : KEYEVENTF_KEYUP;
keybd_event(VK_VOLUME_DOWN, 0, dwFlags, 0);
return 1;
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
int main()
{
SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
}
}
扫描码与虚拟码
win32 api的键盘事件中有两种键盘码,他们的对比如下:
| 对比维度 | 扫描码 (Scan Code) | 虚拟键码 (Virtual-Key Code) |
|---|---|---|
| 本质 | 硬件原始数据 | 操作系统标准化标识 |
| 来源 | 键盘控制器直接生成 | Windows系统定义 |
| 稳定性 | 因键盘而异 | 固定不变 |
| 目的 | 标识物理按键位置 | 标识逻辑功能 |
| API转换 | MapVirtualKey(MAPVK_VSC_TO_VK) | MapVirtualKey(MAPVK_VK_TO_VSC) |
| 典型用途 | 键盘驱动、热键工具、游戏外设 | 应用程序快捷键、UI交互 |
常规的win32窗体程序,使用 keybd_event 或者 SendInput 发送虚拟键码已足够,但在一些涉及更底层的场景中,比如基于DirectX编写的Win32游戏,是无法捕捉到虚拟键码的事件的,它们通常是直接获取底层输入,此时必须使用扫描码。
下面给出一个例子,将F7映射为键盘A输入,不同的是使用的是扫描码输入:
keybd_event的第二个参数为扫描码,使用MapVirtualKey('A', MAPVK_VK_TO_VSC)来获取扫描码。keybd_event的第三个参数需要相与KEYEVENTF_SCANCODE。
#include <stdio.h>
#include <windows.h>
static LRESULT KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
KBDLLHOOKSTRUCT* ks = (KBDLLHOOKSTRUCT*)lParam;
if (ks->vkCode == VK_F7) {
DWORD dwFlags = (wParam == WM_KEYDOWN ? 0 : KEYEVENTF_KEYUP) | KEYEVENTF_SCANCODE;
keybd_event(0, MapVirtualKey('A', MAPVK_VK_TO_VSC), dwFlags, 0);
return 1;
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
int main()
{
SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
}
}