钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
安装Hook的过程
step1:调用SetWindowsHookEx函数安装Hook。
step2:创建钩子函数,处理截获的消息。
常用函数
- SetWindowsHookEx
- GetCurrentThreadId
- CallNextHookEx
- UnhookWindowsHookEx
注:函数具体用法请查阅MSDN
实例1:
安装只截获当前线程的鼠标和键盘消息的钩子
step1:创建一个MFC基于对话框的工程命名为Hook。
step2:在OnInitDialog()函数中安装两个Hook,一个用于截获鼠标一个用于截获键盘。
g_hMouse = SetWindowsHookEx(WH_MOUSE, MouseProc, NULL, GetCurrentThreadId()); //安装一个鼠标的Hook将Hook的句柄保存在全局的g_hMouse中
g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, NULL, GetCurrentThreadId());
step3:在全局区创建钩子函数,处理截获的消息。
LRESULT CALLBACK MouseProc( int nCode, // hook code WPARAM wParam, // message identifier LPARAM lParam // mouse coordinates ) { if(nCode < 0) return CallNextHookEx(g_hMouse, nCode, wParam, lParam); else return 1; }
LRESULT CALLBACK KeyboardProc( int nCode, // hook code WPARAM wParam, // virtual-key code LPARAM lParam // keystroke-message information ) { if(nCode < 0) return CallNextHookEx(g_hKeyboard, nCode, wParam, lParam); //else if(VK_SPACE == wParam) else if(VK_F4 == wParam && ((lParam>>29) & 0x1))//截获Alt+F4 return 1; else return CallNextHookEx(g_hKeyboard, nCode, wParam, lParam); }
注:在Hook函数中返回大于0的数表示此消息已由Hook函数处理了就不会交给Windows窗口过程处理了。如果对此消息不感兴趣可以调用CallNextHookEx将此消息交给Hook链中的下一个Hook函数。
实例2:
安装一个全局的钩子(Hook)
step1:首先建立一个动态链接库用来安装全局钩子。
#include <windows.h> HHOOK g_hMouse = NULL; HHOOK g_hKeyboard = NULL; HWND g_hWnd = NULL; //鼠标钩子函数 LRESULT CALLBACK MouseProc( int nCode, // hook code WPARAM wParam, // message identifier LPARAM lParam // mouse coordinates ) { return 1; } //键盘钩子函数 LRESULT CALLBACK KeyboardProc( int code, // hook code WPARAM wParam, // virtual-key code LPARAM lParam // keystroke-message information ) { if(VK_F2 == wParam) { SendMessage(g_hWnd, WM_CLOSE, 0, 0); UnhookWindowsHookEx(g_hMouse); UnhookWindowsHookEx(g_hKeyboard); } return CallNextHookEx(g_hKeyboard, code, wParam, lParam); } void SetHook(HWND hWnd) { g_hWnd = hWnd; g_hMouse = SetWindowsHookEx(WH_MOUSE, MouseProc, GetModuleHandle("HookDll"), 0); g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, GetModuleHandle("HookDll"), 0); }
step2:将SetHook函数导出。
建立一个扩展名为def的模版定义文件。
LIBARY "HookDll" EXPORTS SetHook
step3:建立一个客户端来安装全局钩子。
建立一个基于对话框的程序,设置链接库文件设置方法见动态链接的创建相关文章。接着声明一个导入函数如下:
_declspec(dllimport)void SetHook(HWND hWnd);
再在OnInitDialog()函数中调用SetHook函数安装全局钩子。
SetHook(m_hWnd);
CallNextHookEx函数的说明
Hook队列的正确的消息处理流程应该如下:
物理击键
↓
钩子管理函数←→钩子A←→钩子B←→钩子C←→钩子D
↓
Window消息处理函数
在钩子A函数中,如果调用CallNextHookEx函数,则会将按键消息传给钩子B;如果不调用CallNextHookEx函数,则钩子B不会得到按键消息,换句话说,钩子B失效了,当然此时的钩子C和钩子D也失效了。为了钩子间和平相处,还是应该在钩子函数里添加CallNextHookEx函数的调用。
再说说钩子函数的返回值的问题。在上面的事例中,钩子A的返回值决定按键消息是否丢弃。返回值0,告诉系统,消息继续传递给Window消息处理函数;返回值1(非0),告诉系统,消息将丢弃,Window消息处理函数得不到按键的消息。
所以说,如果只是统计按键的信息
在钩子函数中的最后直接调用
Return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam)
由后面的钩子函数来决定是否丢弃该消息。(大家和平相处)
如果是屏蔽按键
在钩子函数中进行判断,满足要求后直接
CallNextHookEx(hKeyboardHook, nCode, wParam, lParam)
Return 1
告诉系统,丢弃该消息。当然出于礼貌,在之前还是调用CallNextHookEx函数,以便其他的钩子函数处理该消息
至于修改按键(映射按键),修改参数,调用CallNextHookEx函数是没有用的。因为原本的消息根本就没有修改,你改的只是传给其他钩子函数的消息。而且还非常容易出错。