我们知道,Windows应用程序的工作本质是:消息基础,事件驱动。操作系统为应用程序维护这一个消息列表,应用程序则从这个表中取得消息,并作出相关反应。而钩子则可以监视这些消息,在应用程序获得消息之前截获之,并处理之,处理完了还可以决定该消息的去向。所以利用钩子可以完成很多特殊的功能。
钩子分线程钩子和全局钩子两种。线程钩子只监视某一特定线程的消息,而全局钩子则监视所有线程。全局钩子一般都写在dll中。
一、钩子的安装。
首先我们看一下安装钩子的函数:
HHOOK SetWindowsHookEx(
int idHook, // 钩子类型
HOOKPROC lpfn, // 钩子函数
HINSTANCE hMod, // 模块句柄
DWORD dwThreadId // 线程ID
);
这个函数四个参数:
1、idHook:钩子有很多中类型,WH_CBT,WH_CALLWNDPROC,WH_DEBUG,WH_JOURNALRECORD等等。本文主要讲比较常用的两种:WH_KEYBOARD和WH_MOUSE,分别是监视键盘事件和鼠标事件。
2、lpfn:钩子函数。这是一个我们自己定义的回调函数,当系统监视到我们需要的事件时,就会调用该函数。该函数原型如下:
LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam)
{
//wParam和lParam为所钩的消息的参数,nCode与钩子的类型有关
return CallNextHookEx(hook,nCode,wParam,lParam);
}
这个函数的名字HookPrco可以自己任意取。nCode值与不同的钩子类型相关,wParam和lParam的值和不同的窗口消息相关。这里主要讲下WH_KEYBOARD和WH_MOUSE两个钩子。
1)钩子类型为WH_KEYBOARD时:
键盘钩子,主要监视消息队列中的WM_KEYUP和WM_KEYDOWN消息。
nCode:指定钩子函数是否必须处理该消息,通常为HC_ACTION和HC_NOREMOVE两个值。
wParam:表示按键的虚拟码,即按下了哪个键。
lParam:一个32位的数值。
第0到15位表示你按下某个键的重复次数(当你按住某个键不放时,就会产生重复次数),对于WM_KEYUP消息,该重复次数总是1.
第16到23位表示键的扫描码。
第24位表示该键是否是一个扩展的按键。
第25位到28为保留不使用。
第29位,如果为1,表示同时按下了ALT键,否则为0.
第30位,表示先前该键的状态。为1表示之前该键是按下的,否则为0.
第31位,表示当前该键的状态。0表示按下,1表示释放。
2)钩子类型为WH_MOUSE时:
鼠标钩子,表示监视与鼠标相关的窗口消息,比如WM_MOUSEMOVE,WM_LBUTTONDOWN,WM_LBUTTONUP,WM_LBUTTONDBLCLK,WM_RBUTTONDOWN,WM_MBUTTONDOWN等等。
nCode:意义同上。
wParam:表示鼠标消息的类型,即上面的WM_系列。
lParam:一个指针,指向一个MOUSEHOOKSTRUCT结构。该结构定义如下:
typedef struct tagMOUSEHOOKSTRUCT {
POINT pt; //一个POINT结构,表示当前鼠标指针的x坐标和y坐标。
HWND hwnd; //表示要收到该鼠标消息的窗口的句柄。
UINT wHitTestCode; //一个命中测试值,表示鼠标击中的部位。
ULONG_PTR dwExtraInfo; //和该消息相关的额外信息。
} MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT;
3)钩子函数的返回值。
返回值为0,表示我们的钩子函数已经处理了该消息,则将该消息屏蔽。也就是说,本来应该收到这个消息的窗口现在收不到了。
返回值为非0值,表示放行该消息,继续让窗口收到该消息。
一般地,我们总是返回函数CallNextHookEx的返回值。这里就涉及到一个钩子链的概念。对于同一种类型的钩子,我们可以设置多个,比如这样:
HHOOK hhk1,hhk2;
hhk1=SetWindowsHookEx(WH_KEYBOARD,KeyProc1,NULL,GetCurrentThreadId());
hhk2=SetWindowsHookEx(WH_KEYBOARD,KeyProc2,NULL,GetCurrentThreadId());
这里的KeyProc1和KeyProc2分别是我们自定义的两个钩子函数。
这样就形成了一个钩子链,而后设置的那个钩子(这里的hhk2)位于链首。也就是说,钩子函数KeyProc2会最先处理所钩的消息,如果在KeyProc2中最后调用return CallNextHookEx(hhk2,nCode,wParam,lParam),那么这条消息会继续传给KeyProc1,让它接着来处理这个消息。如果我们不这样调用,那么钩子1将收不到这条消息。
3、hMod
模块句柄。如果是线程钩子,该值可设为NULL。
我们这里讲全局钩子,必须指定为dll模块的句柄。
可以在DllMain入口函数中获取,也可以用hMod=GetModuleHandle("dll名")来获得。
4、dwThreadId
线程ID,指定与钩子函数相关的线程的ID。
全局钩子中设为0,表示监视所有的线程。
该函数返回一个HHOOK类型的句柄。
二、卸载钩子。
在应用程序退出时,不要忘了卸载钩子。
BOOL UnhookWindowsHookEx(HHOOK hhk);
这个函数很简单,只有一个参数hhk,即为SetWindowsHookEx的返回值。
三、一个简单的例子。
1)dll文件的编写。
//dll.cpp
#include
#include
HINSTANCE hdll;
HHOOK hhkKeyboard,hhkMouse;
LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
if((1<<31 & lParam)==0)//键被按下
{
if(wParam=='a' || wParam== 'A')
MessageBox(NULL,TEXT("你按下了A键"),0,0);
}
return CallNextHookEx(hhkKeyboard,nCode,wParam,lParam);
}
LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
if(wParam==WM_LBUTTONDOWN)//鼠标被按下
{
MOUSEHOOKSTRUCT* pMHS=(MOUSEHOOKSTRUCT*)lParam;
int x=pMHS->pt.x;
int y=pMHS->pt.y;
TCHAR szBuffer[50];
_stprintf(szBuffer,TEXT("你的鼠标坐标为%d,%d,窗口句柄为%d"),x,y,pMHS->hwnd);
MessageBox(NULL,szBuffer,0,0);
}
return CallNextHookEx(hhkMouse,nCode,wParam,lParam);
}
BOOL SetHook()
{
hhkKeyboard=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,hdll,0);
hhkMouse =SetWindowsHookEx(WH_MOUSE,MouseProc,hdll,0);
if(hhkKeyboard && hhkMouse)
return TRUE;
else
return FALSE;
}
VOID UnHook()
{
UnhookWindowsHookEx(hhkMouse);
UnhookWindowsHookEx(hhkKeyboard);
}
BOOL WINAPI DllMain(HINSTANCE hInstDll,DWORD fdwReason,PVOID fImpLoad)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
hdll=hInstDll;//获得hdll的值
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
;dll.def文件
EXPORTS
SetHook
UnHook
2)应用程序的编写