C 随记:编译 Win32 DLL,实现全局键盘钩子
头文件 (library.h)
cpp
#ifndef KEYBOARD_HOOK_LIBRARY_H
#define KEYBOARD_HOOK_LIBRARY_H
// 定义EXPORT宏,用于跨平台兼容性
#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
// 回调函数类型定义
// 参数说明:
// - keyCode: 虚拟键码 (Virtual-Key Code)
// - keyName: 键名字符串
// - isKeyDown: 1表示按键按下,0表示按键释放
typedef void (*KeyEventCallback)(int keyCode, const char* keyName, int isKeyDown);
// 导出函数声明
// 安装键盘钩子
EXPORT int InstallKeyboardHook(KeyEventCallback callback);
// 卸载键盘钩子
EXPORT int UninstallKeyboardHook();
// 检查钩子是否处于活动状态
EXPORT int IsHookActive();
// 根据键码获取键名
EXPORT const char* GetKeyName(int keyCode);
// 测试函数,用于验证DLL是否正确加载
EXPORT void TestFunction();
// 运行事件循环,处理Windows消息
EXPORT void RunEventLoop();
#endif // KEYBOARD_HOOK_LIBRARY_H实现文件 (library.c)
cpp
#include <windows.h>
#include <stdio.h>
#include "library.h" // 包含定义了EXPORT宏的头文件
// 全局变量
static HHOOK g_hKeyboardHook = NULL; // 键盘钩子句柄
static HINSTANCE g_hInstance = NULL; // DLL实例句柄
static KeyEventCallback g_callback = NULL; // 用户提供的回调函数指针
static MSG msg; // Windows消息结构体
// 导出 GetKeyName 函数
// 根据虚拟键码返回对应的键名字符串
EXPORT const char* GetKeyName(int keyCode) {
static char keyName[32]; // 静态缓冲区存储键名
switch (keyCode) {
// 特殊功能键映射
case VK_BACK: return "Backspace"; // 退格键
case VK_TAB: return "Tab"; // Tab键
case VK_RETURN: return "Enter"; // 回车键
case VK_SHIFT: return "Shift"; // Shift键
case VK_CONTROL: return "Ctrl"; // Ctrl键
case VK_MENU: return "Alt"; // Alt键
case VK_CAPITAL: return "CapsLock"; // 大写锁定键
case VK_ESCAPE: return "Escape"; // Esc键
case VK_SPACE: return "Space"; // 空格键
case VK_PRIOR: return "PageUp"; // Page Up键
case VK_NEXT: return "PageDown"; // Page Down键
case VK_END: return "End"; // End键
case VK_HOME: return "Home"; // Home键
case VK_LEFT: return "Left"; // 左箭头键
case VK_UP: return "Up"; // 上箭头键
case VK_RIGHT: return "Right"; // 右箭头键
case VK_DOWN: return "Down"; // 下箭头键
case VK_INSERT: return "Insert"; // Insert键
case VK_DELETE: return "Delete"; // Delete键
case VK_LWIN: return "LeftWin"; // 左Windows键
case VK_RWIN: return "RightWin"; // 右Windows键
case VK_NUMPAD0: return "Num0"; // 数字键盘0
case VK_NUMPAD1: return "Num1"; // 数字键盘1
// ... 更多数字键盘键映射
case VK_NUMPAD9: return "Num9"; // 数字键盘9
case VK_MULTIPLY: return "Multiply"; // 乘号(*)键
case VK_ADD: return "Add"; // 加号(+)键
case VK_SUBTRACT: return "Subtract"; // 减号(-)键
case VK_DECIMAL: return "Decimal"; // 小数点(.)键
case VK_DIVIDE: return "Divide"; // 除号(/)键
case VK_F1: return "F1"; // F1功能键
case VK_F2: return "F2"; // F2功能键
// ... 更多F键映射
case VK_F12: return "F12"; // F12功能键
default:
// 处理字母和数字键
if (keyCode >= 0x30 && keyCode <= 0x39) { // 0-9
sprintf(keyName, "%c", keyCode);
return keyName;
}
else if (keyCode >= 0x41 && keyCode <= 0x5A) { // A-Z
sprintf(keyName, "%c", keyCode);
return keyName;
}
else {
// 其他未识别的键码
sprintf(keyName, "Key_0x%02X", keyCode);
return keyName;
}
}
}
// 键盘钩子过程函数
// 这是系统调用的钩子回调函数,当有键盘事件发生时会被触发
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
// 仅当nCode为HC_ACTION且已设置回调函数时处理消息
if (nCode == HC_ACTION && g_callback != NULL) {
// 将lParam转换为KBDLLHOOKSTRUCT结构体指针
KBDLLHOOKSTRUCT *pKeyBoard = (KBDLLHOOKSTRUCT*)lParam;
int keyCode = pKeyBoard->vkCode; // 获取虚拟键码
int isKeyDown = 0; // 初始化按键状态为释放
// 根据wParam判断按键状态
switch (wParam) {
case WM_KEYDOWN: // 普通键按下
case WM_SYSKEYDOWN: // 系统键按下
isKeyDown = 1; // 设置为按下状态
break;
case WM_KEYUP: // 普通键释放
case WM_SYSKEYUP: // 系统键释放
isKeyDown = 0; // 设置为释放状态
break;
default:
break;
}
// 调用用户提供的回调函数,传递键盘事件信息
g_callback(keyCode, GetKeyName(keyCode), isKeyDown);
// 拦截ESC键示例(可选功能)
// 如果需要阻止某个键的默认行为,可以在这里返回1
if (keyCode == VK_ESCAPE && isKeyDown) {
return 1; // 返回1表示拦截该按键,阻止其传递给其他应用程序
}
}
// 调用下一个钩子函数,确保消息继续传递
return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
}
// 添加调试用的测试函数
EXPORT void TestFunction() {
printf("TestFunction called successfully!\n");
}
// 导出函数 - 安装键盘钩子
EXPORT int InstallKeyboardHook(KeyEventCallback callback) {
// 检查是否已经安装了钩子
if (g_hKeyboardHook != NULL) {
return 0; // 钩子已安装,返回0表示失败
}
// 检查回调函数是否为空
if (callback == NULL) {
return -1; // 回调函数不能为空,返回-1表示失败
}
// 保存用户提供的回调函数
g_callback = callback;
// 获取模块句柄,用于安装全局钩子
// 首先尝试获取DLL本身的句柄
HMODULE hModule = GetModuleHandle("keyboard_hook.dll");
if (hModule == NULL) {
// 如果失败,则获取主模块句柄
hModule = GetModuleHandle(NULL);
}
// 安装低级键盘钩子
// WH_KEYBOARD_LL: 低级键盘钩子类型
// LowLevelKeyboardProc: 钩子回调函数
// hModule: DLL模块句柄
// 0: dwThreadId为0表示全局钩子,监视所有线程的键盘输入
g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hModule, 0);
// 检查钩子是否安装成功
if (g_hKeyboardHook == NULL) {
DWORD error = GetLastError(); // 获取错误码
g_callback = NULL; // 清空回调函数
printf("SetWindowsHookEx failed with error: %lu\n", error);
return -2; // 安装失败,返回-2表示失败
}
return (int)g_hKeyboardHook; // 安装成功,返回钩子句柄值
}
// 导出函数 - 卸载键盘钩子
EXPORT int UninstallKeyboardHook() {
// 检查钩子是否已安装
if (g_hKeyboardHook != NULL) {
// 调用API卸载钩子
BOOL result = UnhookWindowsHookEx(g_hKeyboardHook);
g_hKeyboardHook = NULL; // 清空钩子句柄
g_callback = NULL; // 清空回调函数
return result ? 1 : 0; // 根据结果返回1(成功)或0(失败)
}
return 0; // 钩子未安装,返回0表示无需卸载
}
// 导出函数 - 检查钩子是否处于活动状态
EXPORT int IsHookActive() {
// 如果钩子句柄不为NULL,则钩子处于活动状态
return (g_hKeyboardHook != NULL) ? 1 : 0;
}
// 导出函数 - 运行事件循环
EXPORT void RunEventLoop() {
// 持续运行的消息循环
while (1) {
// 处理Windows消息,这对于键盘钩子是必需的
// PeekMessage检查是否有消息在队列中
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg); // 转换消息
DispatchMessage(&msg); // 分发消息
}
// 检查钩子状态
if (!IsHookActive()) {
printf("Keyboard hook unexpectedly stopped!\n");
break; // 如果钩子停止,则退出循环
}
Sleep(10); // 短暂休眠以减少CPU占用
}
}
// DLL入口点(可选但推荐)
// 当DLL被加载或卸载时,系统会调用此函数
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
switch (dwReason) {
case DLL_PROCESS_ATTACH:
printf("DLL_PROCESS_ATTACH\n");
g_hInstance = hModule;
break;
case DLL_PROCESS_DETACH:
// 自动清理钩子
printf("DLL_PROCESS_DETACH\n");
if (g_hKeyboardHook != NULL) {
UnhookWindowsHookEx(g_hKeyboardHook);
g_hKeyboardHook = NULL;
}
break;
}
return TRUE;
}CMake 构建配置
CMake
cmake
# 指定最低CMake版本
cmake_minimum_required(VERSION 4.0)
# 定义项目名称和使用的语言
project(keyboard_hook C)
# 设置C标准为C17
set(CMAKE_C_STANDARD 17)
# 创建共享库(DLL)
add_library(keyboard_hook SHARED library.c)
# 创建测试可执行文件
add_executable(test test.c)
# 链接测试程序到DLL
target_link_libraries(test keyboard_hook)测试程序 (test.c)
cpp
// 包含必要的头文件
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include "library.h" // 包含我们创建的DLL头文件
// 全局变量
static volatile int g_running = 1; // 控制程序运行状态
// 键盘事件回调函数
// 当有键盘事件发生时,此函数会被调用
// 参数说明:
// - keyCode: 虚拟键码
// - keyName: 键名字符串
// - isKeyDown: 1表示按键按下,0表示按键释放
void on_key_event(int keyCode, const char* keyName, int isKeyDown) {
// 根据按键状态确定动作描述
const char* action = isKeyDown ? "Pressed" : "Released";
// 输出键盘事件信息
printf("Key Event: %-8s | Key Code: 0x%02X | Key Name: %s\n", action, keyCode, keyName);
// 检测退出组合键 Ctrl+Q
if (keyCode == 'Q' && isKeyDown) {
// 检查Ctrl键是否同时按下
if (GetAsyncKeyState(VK_CONTROL) & 0x8000) {
printf("\nDetected exit combination Ctrl+Q, preparing to exit...\n");
g_running = 0; // 设置退出标志
}
}
// 检测ESC键退出
if (keyCode == VK_ESCAPE && isKeyDown) {
printf("\nDetected ESC key, preparing to exit...\n");
g_running = 0; // 设置退出标志
}
}
// 主函数
int main() {
// 输出程序标题和功能说明
printf("=== Global Keyboard Hook Test Program ===\n");
printf("Features:\n");
printf("- Program will install a global keyboard hook\n");
printf("- Can monitor all keyboard events (including background)\n");
printf("- Press Ctrl+Q or ESC key to exit the program\n");
printf("- Starting monitoring...\n\n");
// 安装键盘钩子
// 传入我们定义的回调函数on_key_event
int result = InstallKeyboardHook(on_key_event);
// 检查安装结果
if (result <= 0) {
printf("Failed to install keyboard hook! Error code: %d\n", result);
printf("Possible reasons:\n");
printf("- Error code 0: Hook already installed\n");
printf("- Error code -1: Callback function is NULL\n");
printf("- Error code -2: SetWindowsHookEx failed\n");
return 1; // 返回错误码
}
// 输出安装成功信息
printf("Global keyboard hook installed successfully!\n");
printf("Hook handle value: %d\n", result);
printf("Hook status: %s\n", IsHookActive() ? "Active" : "Inactive");
// 测试调用 DLL 中的函数
TestFunction();
printf("Program is running in background, starting to monitor keyboard events...\n\n");
// 运行事件循环
// 这是钩子正常工作的关键部分
RunEventLoop();
// 卸载钩子
printf("\nUninstalling keyboard hook...\n");
result = UninstallKeyboardHook();
// 检查卸载结果
if (result) {
printf("Keyboard hook uninstalled successfully!\n");
} else {
printf("Failed to uninstall keyboard hook!\n");
}
printf("Program exited. Press any key to close window...\n");
// 等待用户按键后退出
_getch();
return 0;
}