天天看點

利用DLL建構全局鈎子

我們知道,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。