全局钩子是Windows编程中的一种技术,用于监控系统或特定应用程序中的特定事件,例如键盘、鼠标输入等。在Windows API中,钩子分为多种类型,包括系统级钩子(全局钩子)和个人级钩子(局部钩子)。通常,全局钩子的实现需要通过动态链接库(DLL)来实现代码注入,以便在其他进程中运行钩子函数。然而,并非所有全局钩子都需要DLL支持。
在《Windows高级编程指南》(《Windows核心编程》)一书中,Jeffrey Richter详细介绍了如何使用全局消息钩子进行代码注入。虽然大多数全局钩子确实依赖于DLL,但有四种特殊类型的钩子,它们可以在没有DLL的情况下正常工作。这些钩子包括:
1. WH_JOURNALPLAYBACK:用于记录和回放键盘和鼠标事件。
2. WH_JOURNALRECORD:用于捕获键盘和鼠标事件的原始数据。
3. WH_KEYBOARD_LL:低级别键盘钩子,监视键盘输入。
4. WH_MOUSE_LL:低级别鼠标钩子,监视鼠标输入。
这四种钩子之所以不需要DLL,是因为它们的钩子函数在安装钩子的线程上下文中执行。这意味着,当你在一个线程中安装了这些钩子,相应的钩子函数就会在这个线程中被调用,而不是在被钩住的进程中。因此,这些钩子无法达到代码注入的目的,也就无需依赖DLL来实现跨进程功能。
下面是一个使用WH_KEYBOARD_LL创建的不依赖DLL的底层键盘钩子的C++示例代码:
```cpp
// kbhook.cpp
#define _WIN32_WINNT 0400
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
DWORD g_main_tid = 0;
HHOOK g_kb_hook = 0;
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
// 无需在此处理,因为不需要DLL
return TRUE;
}
BOOL CALLBACK con_handler(DWORD dwCtrlType)
{
PostThreadMessage(g_main_tid, WM_QUIT, 0, 0);
return TRUE;
}
int main()
{
g_main_tid = GetCurrentThreadId();
SetConsoleCtrlHandler(con_handler, TRUE);
g_kb_hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, GetCurrentThreadId());
if (!g_kb_hook)
{
printf("Failed to set keyboard hook\n");
return -1;
}
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UnhookWindowsHookEx(g_kb_hook);
return 0;
}
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 在这里处理键盘事件
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
```
这个示例中,`LowLevelKeyboardProc`是键盘钩子的回调函数,它会接收到所有的键盘输入事件。`SetWindowsHookEx`函数用于安装钩子,参数`NULL`表示不需要DLL,`GetCurrentThreadId()`确保钩子在当前线程中运行。
总结来说,虽然全局钩子通常与DLL相关联,但WH_JOURNALPLAYBACK、WH_JOURNALRECORD、WH_KEYBOARD_LL和WH_MOUSE_LL这四种钩子能够独立工作,不依赖于DLL,因为它们在安装钩子的线程上下文中执行。这种特性使得开发者在某些场景下可以避免使用DLL,简化了程序设计。