C++ 键盘钩子
Posted
技术标签:
【中文标题】C++ 键盘钩子【英文标题】:C++ KeyBoard hook 【发布时间】:2014-10-21 18:11:43 【问题描述】:主题:将某些键替换为另一个键值。
例如如果我按 P 应该是 F24。
当我尝试从 .ini 文件中加载键值时,钩子不再是全局的。仅当 winapi 表单处于焦点时才有效。
我的 DLL 代码:
extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardHook(int, WPARAM, LPARAM);
extern "C" __declspec(dllexport) void loadSettings(LPSTR);
bool shouldUpdateKey = false;
int ArcherKey;
LRESULT CALLBACK KeyboardHook(int code, WPARAM wParam, LPARAM lParam)
if ((lParam >> 20))
if (wParam == ArcherKey)
shouldUpdateKey = shouldUpdateKey ? false : true;
if (shouldUpdateKey)
MessageBox(NULL, L"ArcherKey", L"", MB_OK);
keybd_event(0x87, 45, 1, 0); //press F24
return 1;
return CallNextHookEx(NULL, code, wParam, lParam);
LPSTR GetValueFromINI(LPSTR FileName, LPSTR Section, LPSTR Key)
char *key;
key = (char *)malloc(256);
GetPrivateProfileStringA(Section, Key, NULL, key, 256, FileName);
return key;
free(key);
void loadSettings(LPSTR FileName)
ArcherKey = atoi(GetValueFromINI(FileName, "HotKey", "Archer key"));
我使用 shouldUpdateKey 来避免 x2 回调(按下和按下按键时)调用。我也尝试添加这个语句 if (lParam >>31) ^ 1,但是这个语句总是错误的。
.exe代码:
LRESULT(*pKeybHook)(int, WPARAM, LPARAM);
HHOOK hhookMsg;
void(*loadSettings)(LPSTR);
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
/* default code */
HMODULE dll = LoadLibrary(_T("MainHookDLL.dll"));
if (dll)
pKeybHook = (LRESULT(*)(int, WPARAM, LPARAM)) GetProcAddress(dll, "_KeyboardHook@12");
loadSettings = (void(*)(LPSTR)) GetProcAddress(dll, "loadSettings");
loadSettings("C:\\Settings.ini");
hhookMsg = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)(pKeybHook), dll, 0);
/* defult code */
UnhookWindowsHookEx(hhookMsg); // unhook
FreeLibrary(dll);
return (int) msg.wParam;
Settings.ini 结构:
[HotKey]
Archer key=80
所以我的问题: 如果尝试从文件加载设置,则挂钩仅在活动的 winapi 窗口中有效。它显示 MessageBox\etc,但仅以活动的 winapi 形式显示。 如果将 wParam == ArcherKey 替换为 wParam == 80,它将在所有应用程序中全局工作。 我调试我的应用程序,从 .ini 文件加载后,我的 ArcherKey = 80。所以我真的不明白我到底犯了什么错误。
【问题讨论】:
【参考方案1】:据我回忆,如果钩子是全局的,则包含 HOOKPROC 的 DLL 会在所有其他进程中加载。这意味着您在内存中有多个 DLL 实例。由于您从应用程序中调用 loadSettings(..),因此 ArcherKey 的值仅针对该进程进行初始化。这会导致您观察到的行为。
要改变这一点,您应该将 DllMain(..) 函数修改为如下所示:
BOOL WINAPI DllMain(HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpReserved ) // reserved
switch( fdwReason )
case DLL_PROCESS_ATTACH:
loadSettings("C:\\Settings.ini");
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;
return TRUE;
这会为所有正在安装挂钩的进程初始化 ArcherKey 的值,因为 Windows 在加载 DLL 时会调用 DllMain。出于测试目的,您可以添加 MessageBeep(0);在调用 loadSettings(..) 以验证该部分代码是否已执行之前。
快速浏览SetWindowsHookEx(..) 的文档让我的恐惧成真:如果您正在编译一个 32 位 DLL,您将不能够挂钩 64 位进程,反之亦然。为此,您必须使用不同名称的 HOOKPROC 实现 64 位版本的 dll。
【讨论】:
哦,我应该阅读更多文档 -.- 我只是在 DLL 条目 func 中添加 MessageBox 时理解这一点,每次我在新窗口中执行键盘操作时,dll 都会创建新实例(如果这是正确的含义)。谢谢,现在我知道我该怎么做了。【参考方案2】:钩子被“注入”到其他进程中,这意味着您的整个 DLL 将被加载到所有相关进程中,就好像进程本身(例如 Notepad.exe)调用了 LoadLibrary()。所以在这种情况下(在其他进程中,例如 Notepad.exe),你的设置不会被加载,所以 ArcherKey 不会被初始化,所以消息框不会出现。
所以你必须让你的 DLL 来做初始化,而不是一个单独的 .exe。您可以通过 DLL_PROCESS_ATTACH 上的 DllMain 初始化 ArcherKey(加载您的设置)(尽管此时需要注意哪些 API 是安全的 - 大多数会导致加载其他 DLL 的任何调用都是禁止的),或者您可以大致添加代码:
static DWORD initialized = 0;
static int ArcherKey;
LRESULT CALLBACK KeyboardHook(int code, WPARAM wParam, LPARAM lParam)
if (!initialized)
loadSettings();
...
尽管这段代码根本不可取,因为长时间运行的钩子至少是相当糟糕的形式,并且可能会导致问题(例如,停止该进程)。或者,您可以将数据放在已知的共享位置。编辑:在the accepted answer to a similar question 中有一些关于共享值方式的好建议。
【讨论】:
谢谢,我试试这个。【参考方案3】:LPSTR GetValueFromINI(LPSTR FileName, LPSTR Section, LPSTR Key)
char *key;
key = (char *)malloc(256);
GetPrivateProfileStringA(Section, Key, NULL, key, 256, FileName);
return key;
free(key); // <- will never happen
ArcherKey = atoi(GetValueFromINI(...)); // <- does not clean up
内存泄漏
【讨论】:
以上是关于C++ 键盘钩子的主要内容,如果未能解决你的问题,请参考以下文章