Skip to content

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;
}

最后编辑时间:

Version 4.3