如何使用 SetWindowsHookExA 和 LowLevelKeyboardProc 分配多个 LowLevel 热键
How to assign multiple LowLevel HotKeys with SetWindowsHookExA and LowLevelKeyboardProc
我正在尝试为 LowLevel 全局热键编写 class。这个想法是 Class 的一个实例代表一个热键或热键序列,例如 Alt+SHift+G
并且可以工作。当我现在为第二个 HotKey 创建 class 的第二个实例时,它会覆盖我的第一个,只能触发第二个或最后一个。知道如何扩展它以处理更多实例吗?我可以使 LowLevelKeyboardProc 成为我的 class 的非静态成员方法吗?也许这会解决我的问题。
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;
}
可以同时注册多个钩子,它们会被链接在一起(这就是为什么每个钩子都必须调用 CallNextHookEx()
,所以链中的下一个钩子会被调用)。
您的问题不是因为您注册了多个挂钩,而是因为您使用的是全局变量 mWinKeyHandlerReference
,它一次只能引用一个 class 实例。它被设置为引用最后创建的实例,这意味着所有挂钩都将它们的事件发送到该实例。
不,您不能直接使用非静态 class方法作为挂钩过程,因为隐式this
需要的参数,钩子不知道如何传入。
你 可以 做的是让 class 创建一个 thunk - 一块 可执行文件 动态分配的内存VirtualAlloc()
with PAGE_EXECUTE
rights containing just enough CPU instructions to forward its input parameters to a non-static method using the object's this
pointer - 然后你可以使用那个 thunk 作为挂钩程序。这将允许您创建使用单个 this
指针的每个实例挂钩过程。
见Callback of member functions through 3d party library and Thunking in Win32: Simplifying Callbacks to Non-static Member Functions。
我正在尝试为 LowLevel 全局热键编写 class。这个想法是 Class 的一个实例代表一个热键或热键序列,例如 Alt+SHift+G
并且可以工作。当我现在为第二个 HotKey 创建 class 的第二个实例时,它会覆盖我的第一个,只能触发第二个或最后一个。知道如何扩展它以处理更多实例吗?我可以使 LowLevelKeyboardProc 成为我的 class 的非静态成员方法吗?也许这会解决我的问题。
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;
}
可以同时注册多个钩子,它们会被链接在一起(这就是为什么每个钩子都必须调用 CallNextHookEx()
,所以链中的下一个钩子会被调用)。
您的问题不是因为您注册了多个挂钩,而是因为您使用的是全局变量 mWinKeyHandlerReference
,它一次只能引用一个 class 实例。它被设置为引用最后创建的实例,这意味着所有挂钩都将它们的事件发送到该实例。
不,您不能直接使用非静态 class方法作为挂钩过程,因为隐式this
需要的参数,钩子不知道如何传入。
你 可以 做的是让 class 创建一个 thunk - 一块 可执行文件 动态分配的内存VirtualAlloc()
with PAGE_EXECUTE
rights containing just enough CPU instructions to forward its input parameters to a non-static method using the object's this
pointer - 然后你可以使用那个 thunk 作为挂钩程序。这将允许您创建使用单个 this
指针的每个实例挂钩过程。
见Callback of member functions through 3d party library and Thunking in Win32: Simplifying Callbacks to Non-static Member Functions。