首页 > 解决方案 > 如何使用 SetWindowsHookExA 和 LowLevelKeyboardProc 分配多个 LowLevel 热键

问题描述

我正在尝试为 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;
}

标签: c++windowshotkeyssetwindowshookex

解决方案


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

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

不,您不能直接使用非静态类方法作为钩子过程,因为this需要隐式参数,钩子不知道如何传递。

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

请参阅通过 3d 方库回调成员函数Win32 中的 Thunking:简化对非静态成员函数的回调


推荐阅读