如何使用 SetWindowsHookEx 和 LowLevelKeyboardProc 分配多个低级热键

Posted

技术标签:

【中文标题】如何使用 SetWindowsHookEx 和 LowLevelKeyboardProc 分配多个低级热键【英文标题】:How to assign multiple LowLevel HotKeys with SetWindowsHookExA and LowLevelKeyboardProc 【发布时间】:2020-07-13 16:40:20 【问题描述】:

我正在尝试为 LowLevel Global HotKeys 编写一个类。想法是该类的一个实例代表一个热键或热键序列,如Alt+SHift+G 并且有效。当我现在为第二个 HotKey 创建该类的第二个实例时,它会覆盖我的第一个,只能触发第二个或最后一个。知道如何扩展它以使用更多实例吗?我可以让 LowLevelKeyboardProc 成为我班级的非静态成员方法吗?也许这会解决我的问题。

WinKeyHandler.h:

class WinKeyHandler : public AbstractKeyHandler

public:
    WinKeyHandler();
    ~WinKeyHandler() override;

    bool registerKey(const QKeySequence &keySequence) override;
    bool isHotkeyTriggered(void* message) override;

private:
    KeySequenceToWinKeyCodeTranslator mKeyCodeMapper;
    static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
    HHOOK mLowLevelKeyboardHook;

    QVector<DWORD> mPressedKeys;
    unsigned int mPressedModifiers;
    unsigned int mPressedKey;
    bool mTriggered;
    KeyCodeCombo mKeySequence;

    DWORD translateVkCode(DWORD vkcode);
    void handleKeySequence();
    void handleKeyPress(DWORD vkCode);
    void handleKeyRelease(DWORD vkCode);
    void resetKeys();
;

WinKeyHandler.cpp:

WinKeyHandler * mWinKeyHandlerReference;

WinKeyHandler::WinKeyHandler()

    resetKeys();
    mWinKeyHandlerReference = this;


WinKeyHandler::~WinKeyHandler()

    UnhookWindowsHookEx(mLowLevelKeyboardHook);


bool WinKeyHandler::registerKey(const QKeySequence &keySequence)

    mKeySequence = mKeyCodeMapper.map(keySequence);
    mLowLevelKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, nullptr, 0);

    return mLowLevelKeyboardHook != nullptr;


bool WinKeyHandler::isHotkeyTriggered(void* message)

    Q_UNUSED(message)

    return false;


LRESULT CALLBACK WinKeyHandler::LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)

    if (nCode == HC_ACTION)
    
        auto vkCode = mWinKeyHandlerReference->translateVkCode(PKBDLLHOOKSTRUCT(lParam)->vkCode);
        switch (wParam)
        
            case WM_KEYDOWN:
            case WM_SYSKEYDOWN:
                    mWinKeyHandlerReference->handleKeyPress(vkCode);
                break;
            case WM_KEYUP:
                mWinKeyHandlerReference->handleKeyRelease(vkCode);
                break;
        

        mWinKeyHandlerReference->handleKeySequence();
    
    return CallNextHookEx(nullptr, nCode, wParam, lParam);


DWORD WinKeyHandler::translateVkCode(DWORD vkCode)

    if(vkCode == VK_LCONTROL || vkCode == VK_RCONTROL) 
        return VK_CONTROL;
     else if(vkCode == VK_LSHIFT || vkCode == VK_RSHIFT) 
        return VK_SHIFT;
     else if(vkCode == VK_LMENU || vkCode == VK_RMENU) 
        return VK_MENU;
     else 
        return vkCode;
    


void WinKeyHandler::handleKeySequence()

    if(mKeySequence.modifier == mPressedModifiers && mKeySequence.key == mPressedKey) 
        if(!mTriggered) 
            emit triggered();
            resetKeys();
        
        mTriggered = true;
     else 
        mTriggered = false;
    


void WinKeyHandler::handleKeyPress(DWORD vkCode)

    if(!mPressedKeys.contains(vkCode)) 
        mPressedKeys.append(vkCode);

        if(vkCode == VK_CONTROL || vkCode == VK_MENU || vkCode == VK_SHIFT) 
            mPressedModifiers |= vkCode;
         else 
            mPressedKey = vkCode;
        
    


void WinKeyHandler::handleKeyRelease(DWORD vkCode)

    if(mPressedKeys.contains(vkCode)) 
        mPressedKeys.removeOne(vkCode);

        if(vkCode == VK_CONTROL || vkCode == VK_MENU || vkCode == VK_SHIFT) 
            mPressedModifiers ^= vkCode;
         else 
            mPressedKey = 0;
        
    


void WinKeyHandler::resetKeys()

    mPressedKey = 0;
    mPressedModifiers = 0;

【问题讨论】:

docs.microsoft.com/en-us/windows/win32/api/winuser/… @HansPassant RegisterHotKey 对我不起作用,因为它无法捕获像 PrintKey 这样的键,这对我的用例至关重要。我必须带着低级程序去这里。 @HansPassant 最后,RegisterHotKey 是我用例的正确解决方案,我已经实现了它,但没有为 PrintKey 得到任何东西,事实证明,它不是 VK_PRINT 而是 VK_SNAPSHOT。 【参考方案1】:

可以同时注册多个钩子,它们会被链接在一起(这就是为什么每个钩子都必须调用CallNextHookEx(),所以链中的下一个钩子会被调用)。

你的问题不是因为你注册了多个钩子,而是因为你使用了一个全局变量mWinKeyHandlerReference,它一次只能引用一个类实例。它被设置为引用最后创建的实例,这意味着所有钩子都将它们的事件发送到那个实例。

不,您不能直接使用 非静态 类方法作为钩子过程,因为需要隐含的 this 参数,该钩子不知道怎么进去。

可以做的是让类创建一个thunk - 通过VirtualAlloc()动态分配的一个可执行内存块,PAGE_EXECUTE权限包含足够的CPU使用对象的 this 指针将其输入参数转发到非静态方法的说明 - 然后您可以使用该 thunk 作为挂钩过程。这将允许您创建使用单个 this 指针的每个实例的挂钩过程。

请参阅 Callback of member functions through 3d party library 和 Thunking in Win32: Simplifying Callbacks to Non-static Member Functions。

【讨论】:

感谢您的解释和链接,将尝试。 再次感谢您的回答。最后,我恢复使用 RegisterHotKey,结果证明它适用于我的 useCase,并且在我的情况下实现 Thunk 似乎有点矫枉过正,而且代码也不是很简单。我将您的答案标记为正确,以防有人必须使用 LowLevel 函数。

以上是关于如何使用 SetWindowsHookEx 和 LowLevelKeyboardProc 分配多个低级热键的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 SetWindowsHookEx api 锁定 CTRL+ALT+DEL?

调用外部 SetWindowsHookEx 和 GetModuleHandle 的 PInvoke 错误

如何从 SetWindowsHookEx 回调调用函数指针

SetWindowsHookEx DLL 卸载

无法在 Borland C++ Builder 中使用 SetWindowsHookEx 和 LowLevelKeyboardProc

SetWindowsHookEx - 防病毒问题