我們知道,Windows應用程式的工作本質是:消息基礎,事件驅動。作業系統為應用程式維護這一個消息清單,應用程式則從這個表中取得消息,并作出相關反應。而鈎子則可以監視這些消息,在應用程式獲得消息之前截獲之,并處理之,處理完了還可以決定該消息的去向。是以利用鈎子可以完成很多特殊的功能。
鈎子分線程鈎子和全局鈎子兩種。線程鈎子隻監視某一特定線程的消息,而全局鈎子則監視所有線程。全局鈎子一般都寫在dll中。
一、鈎子的安裝。
首先我們看一下安裝鈎子的函數:
<span style="font-size:24px;">HHOOK SetWindowsHookEx(
int idHook, // 鈎子類型
HOOKPROC lpfn, // 鈎子函數
HINSTANCE hMod, // 子產品句柄
DWORD dwThreadId // 線程ID
);</span>
這個函數四個參數:
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結構。該結構定義如下:
<span style="font-size:24px;">typedef struct tagMOUSEHOOKSTRUCT {
POINT pt; //一個POINT結構,表示目前滑鼠指針的x坐标和y坐标。
HWND hwnd; //表示要收到該滑鼠消息的視窗的句柄。
UINT wHitTestCode; //一個命中測試值,表示滑鼠擊中的部位。
ULONG_PTR dwExtraInfo; //和該消息相關的額外資訊。
} MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT; </span>
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 <windows.h>
#include <tchar.h>
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)應用程式的編寫
應用程式編寫比較簡單,隻要在項目屬性中添加依賴lib,并且聲明導入函數,就可以使用SetHook安裝鈎子了。在程式退出的時候,要調用UnHook。