You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows 11全局键盘钩子失效求助:如何实现全局热键文本注入?

全局热键注入字符串:从WH_KEYBOARD_LL到WH_KEYBOARD的解决方案

问题背景

需求是实现LeftAlt+1和LeftAlt+2两组全局组合键,按下时向当前激活的程序注入指定字符串。现有基于WH_KEYBOARD_LL钩子的实现仅能在测试程序控制台内生效,文本会输出到测试程序窗口而非目标程序,计划改用WH_KEYBOARD钩子解决该问题。

现有代码

钩子DLL代码(PernumLib.dll)

#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

#include <windows.h>
#include <iostream>

using namespace std;
HHOOK hHook{ NULL };
const DWORD L1 = 49;
const DWORD L2 = 50;

extern "C" __declspec(dllexport) 
    LRESULT CALLBACK KeyboardHookProc(int code, WPARAM wParam, LPARAM lParam)
{
    if (code < 0) return CallNextHookEx(hHook, code, wParam, lParam);

    if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
    {
        KBDLLHOOKSTRUCT* kbdStruct = (KBDLLHOOKSTRUCT*)lParam;
        DWORD vkCode = kbdStruct->vkCode;  // virtual-key code
        if (kbdStruct->flags & LLKHF_ALTDOWN && (vkCode == L1 || vkCode == L2))
        {
            if (vkCode == L1) cout << "First text";
            else cout << "Second text";
        }
    }
    return CallNextHookEx(hHook, code, wParam, lParam);
}

测试程序代码

#include <windows.h>
#include <iostream>

using namespace std;

int main()
{
    HHOOK hhook = 0;

    LPCWSTR dllPath = L"PernumLib.dll";
    static HINSTANCE hinstDLL = LoadLibraryW(dllPath);

    HOOKPROC keyboardHookProc = (HOOKPROC)GetProcAddress(hinstDLL, "KeyboardHookProc");

    hhook = SetWindowsHookExW(WH_KEYBOARD_LL, keyboardHookProc, hinstDLL, 0);
    if (hhook == NULL)
    {
        cout << "SetWindowsHookExA failure!\n";
        Sleep(3000);
        return -1;
    }

    while (GetMessage(NULL, NULL, 0, WM_KEYLAST));

    //if (hhook) UnhookWindowsHookEx(hhook);
    return 0;
}

问题分析

  • WH_KEYBOARD_LL是用户态全局钩子,其回调函数始终在安装钩子的测试进程上下文执行,因此cout输出会直接写入测试程序控制台,而非当前激活的目标窗口。
  • 已排除32/64位架构不兼容问题,钩子本身可捕获全局按键,但输出上下文不符合需求。
  • WH_KEYBOARD是线程注入型全局钩子,钩子DLL会被注入到每个目标进程,回调函数在目标进程上下文执行,能实现向当前激活窗口注入文本。

解决方案:改用WH_KEYBOARD钩子

修改后的钩子DLL代码

#include "pch.h"
#include <windows.h>

// 模拟Unicode键盘输入,向当前窗口注入字符串
void SendString(const wchar_t* text) {
    size_t charCount = wcslen(text);
    INPUT* inputArray = new INPUT[charCount * 2];
    ZeroMemory(inputArray, sizeof(INPUT) * charCount * 2);

    for (size_t i = 0; i < charCount; ++i) {
        // 按下字符
        inputArray[i * 2].type = INPUT_KEYBOARD;
        inputArray[i * 2].ki.wVk = 0;
        inputArray[i * 2].ki.wScan = text[i];
        inputArray[i * 2].ki.dwFlags = KEYEVENTF_UNICODE;
        // 释放字符
        inputArray[i * 2 + 1].type = INPUT_KEYBOARD;
        inputArray[i * 2 + 1].ki.wVk = 0;
        inputArray[i * 2 + 1].ki.wScan = text[i];
        inputArray[i * 2 + 1].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
    }

    SendInput(charCount * 2, inputArray, sizeof(INPUT));
    delete[] inputArray;
}

extern "C" __declspec(dllexport) 
LRESULT CALLBACK KeyboardHookProc(int code, WPARAM wParam, LPARAM lParam) {
    if (code < 0) {
        return CallNextHookEx(NULL, code, wParam, lParam);
    }

    KBDHOOKSTRUCT* kbdStruct = (KBDHOOKSTRUCT*)lParam;
    // 检查LeftAlt是否处于按下状态
    BOOL isLeftAltDown = (GetKeyState(VK_LMENU) & 0x8000) != 0;
    
    if (isLeftAltDown && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) {
        if (kbdStruct->vkCode == '1') {
            SendString(L"First text");
            return 1; // 拦截原按键,避免输入"1"
        } else if (kbdStruct->vkCode == '2') {
            SendString(L"Second text");
            return 1; // 拦截原按键,避免输入"2"
        }
    }

    return CallNextHookEx(NULL, code, wParam, lParam);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hModule); // 减少不必要的线程通知
            break;
        case DLL_PROCESS_DETACH:
            break;
        default:
            break;
    }
    return TRUE;
}

修改后的测试程序代码

#include <windows.h>
#include <iostream>
#include <wchar.h>

using namespace std;

int main() {
    HHOOK hhook = NULL;
    WCHAR dllFullPath[MAX_PATH];
    
    // 获取DLL绝对路径,避免其他进程找不到文件
    GetFullPathNameW(L"PernumLib.dll", MAX_PATH, dllFullPath, NULL);

    HINSTANCE hinstDLL = LoadLibraryW(dllFullPath);
    if (!hinstDLL) {
        cout << "LoadLibraryW failed! Error: " << GetLastError() << endl;
        Sleep(3000);
        return -1;
    }

    HOOKPROC keyboardHookProc = (HOOKPROC)GetProcAddress(hinstDLL, "KeyboardHookProc");
    if (!keyboardHookProc) {
        cout << "GetProcAddress failed! Error: " << GetLastError() << endl;
        FreeLibrary(hinstDLL);
        Sleep(3000);
        return -1;
    }

    // 安装WH_KEYBOARD全局钩子,0表示注入所有同架构线程
    hhook = SetWindowsHookExW(WH_KEYBOARD, keyboardHookProc, hinstDLL, 0);
    if (!hhook) {
        cout << "SetWindowsHookExW failed! Error: " << GetLastError() << endl;
        FreeLibrary(hinstDLL);
        Sleep(3000);
        return -1;
    }

    cout << "Hook installed successfully. Press any key to exit..." << endl;
    MSG msg;
    while (GetMessageW(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    // 清理资源
    if (hhook) {
        UnhookWindowsHookEx(hhook);
    }
    FreeLibrary(hinstDLL);
    return 0;
}

关键注意事项

  • 架构匹配:确保DLL和测试程序编译为相同架构(x86/x64),否则无法注入到对应架构的进程。
  • 注入限制:部分高安全级进程(如带沙箱的浏览器)可能阻止DLL注入,此类场景下WH_KEYBOARD钩子无法生效。
  • DLL路径:必须使用绝对路径加载DLL,否则其他进程可能找不到DLL文件导致注入失败。
  • 权限要求:若目标进程为管理员权限,测试程序也需以管理员权限运行才能完成注入。

内容的提问来源于stack exchange,提问作者Kalle Svensson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 08:35:38