天天看點

C++ Windows Hook 消息鈎子 詳解

本文完整測試工程的源碼免積分下載下傳位址:http://download.csdn.net/detail/zy_dreamer/5336484

我們先來簡單的了解一下基本概念:

Hook是WINDOWS提供的一種消息處理機制,它使得程式員可以使用子過程來監視系統消息,并在消息到達目标過程前得到處理。

HOOK鍊 

WINDOWS提供了幾種不同類型的HOOKS;不同的HOOK可以處理不同的消息。例如,WH_MOUSE HOOK用來監視滑鼠消息。 

WINDOWS為這幾種HOOKS維護着各自的HOOK鍊。HOOK鍊是一個由應用程式定義的回調函數隊列,當某種類型的消息發生時,WINDOWS向此種類型的HOOK鍊的第一個函數發送該消息,在第一函數處理完該消息後由該函數向連結清單中的下一個函數傳遞消息,依次向下。如果鍊中某個函數沒有向下傳送該消息,那麼連結清單中後面的函數将得不到此消息。(對于某些類型的HOOK,不管HOOK鍊中的函數是否向下傳遞消息,與此類型HOOK聯系的所有HOOK函數都會收到系統發送的消息)

HOOK過程 

為了攔截特定的消息,你可以使用SetWindowsHookEx函數在該類型的HOOK鍊中安裝你自己的HOOK函數。

該函數MSDN中的描述

The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain. You would install a hook procedure to monitor the system for certain types of events. These events are associated either with a specific thread or with all threads in the same desktop as the calling thread. 

Syntax

HHOOK SetWindowsHookEx(          int idHook,
    HOOKPROC lpfn,
    HINSTANCE hMod,
    DWORD dwThreadId
);
Parameters

idHook
[in] Specifies the type of hook procedure to be installed. This parameter can be one of the following values. 
WH_CALLWNDPROC
Installs a hook procedure that monitors messages before the system sends them to the destination window procedure. For more information, see the CallWndProc hook procedure.
WH_CALLWNDPROCRET
Installs a hook procedure that monitors messages after they have been processed by the destination window procedure. For more information, see the CallWndRetProc hook procedure.
WH_CBT
Installs a hook procedure that receives notifications useful to a computer-based training (CBT) application. For more information, see the CBTProc hook procedure.
WH_DEBUG
Installs a hook procedure useful for debugging other hook procedures. For more information, see the DebugProc hook procedure.
WH_FOREGROUNDIDLE
Installs a hook procedure that will be called when the application's foreground thread is about to become idle. This hook is useful for performing low priority tasks during idle time. For more information, see the ForegroundIdleProc hook procedure. 
WH_GETMESSAGE
Installs a hook procedure that monitors messages posted to a message queue. For more information, see the GetMsgProc hook procedure.
WH_JOURNALPLAYBACK
Installs a hook procedure that posts messages previously recorded by a WH_JOURNALRECORD hook procedure. For more information, see the JournalPlaybackProc hook procedure.
WH_JOURNALRECORD
Installs a hook procedure that records input messages posted to the system message queue. This hook is useful for recording macros. For more information, see the JournalRecordProc hook procedure.
WH_KEYBOARD
Installs a hook procedure that monitors keystroke messages. For more information, see the KeyboardProc hook procedure.
WH_KEYBOARD_LL
Windows NT/2000/XP: Installs a hook procedure that monitors low-level keyboard input events. For more information, see the LowLevelKeyboardProc hook procedure.
WH_MOUSE
Installs a hook procedure that monitors mouse messages. For more information, see the MouseProc hook procedure.
WH_MOUSE_LL
Windows NT/2000/XP: Installs a hook procedure that monitors low-level mouse input events. For more information, see the LowLevelMouseProc hook procedure.
WH_MSGFILTER
Installs a hook procedure that monitors messages generated as a result of an input event in a dialog box, message box, menu, or scroll bar. For more information, see the MessageProc hook procedure.
WH_SHELL
Installs a hook procedure that receives notifications useful to shell applications. For more information, see the ShellProc hook procedure.
WH_SYSMSGFILTER
Installs a hook procedure that monitors messages generated as a result of an input event in a dialog box, message box, menu, or scroll bar. The hook procedure monitors these messages for all applications in the same desktop as the calling thread. For more information, see the SysMsgProc hook procedure.
lpfn
[in] Pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a thread created by a different process, the lpfn parameter must point to a hook procedure in a DLL. Otherwise, lpfn can point to a hook procedure in the code associated with the current process. 
hMod
[in] Handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process. 
dwThreadId
[in] Specifies the identifier of the thread with which the hook procedure is to be associated. If this parameter is zero, the hook procedure is associated with all existing threads running in the same desktop as the calling thread. 
Return Value

If the function succeeds, the return value is the handle to the hook procedure. 

If the function fails, the return value is NULL. To get extended error information, call GetLastError.
           

這裡我來簡單的解釋一下:

函數的功能描述是:SetWindowsHookEx函數安裝一個程式自定義的鈎子過程到鈎子鍊。你可以安裝一個鈎子過程來監視系統的特定消息。可以監視某一個特定的線程的消息,或者是與這個消息相關的目前運作的所有線程。

函數一共有四個參數

參數1:idHook

定義了鈎子過程的類型,也就是這個鈎子将要監視哪種消息

參數2:lpfn

它是一個鈎子過程的指針。如果dwThreadId也就是第四個參數的值是0或者是由其它程序建立的線程的ID,那麼這個參數必須是指向一個位于DLL中的鈎子過程的指針,否則這個指針可以是目前程序代碼空間中定義的一個鈎子過程的位址。

參數3:hMod

是一個包含了lpfn所指向的鈎子過程的DLL的句柄。如果第四個參數線程ID的值是由目前程序建立的線程的ID值并且鈎子過程是在目前程序中定義的,那麼這個參數必須設定成NULL。

參數4:dwThreadId

指明鈎子過程要監視的線程ID。如果是0那麼鈎子過程将監視所有桌面運作的線程。

傳回值:

成功傳回鈎子過程的句柄。

失敗傳回NULL,獲得額外資訊,調用GetLastError

lpfn指向鈎子回調函數的函數位址,這個函數用來對鈎子檢測到的消息進行處理。MSDN中函數的原型如下,這裡以鍵盤檢測函數的原型為例:

The KeyboardProc hook procedure is an application-defined or library-defined callback function used with theSetWindowsHookEx function. The system calls this function whenever an application calls the GetMessage orPeekMessage function and there is a keyboard message (WM_KEYUP or WM_KEYDOWN) to be processed.

The HOOKPROC type defines a pointer to this callback function. KeyboardProc is a placeholder for the application-defined or library-defined function name.

Syntax

LRESULT CALLBACK KeyboardProc(      

    int code,
    WPARAM wParam,
    LPARAM lParam
);      

Parameters

code
[in] Specifies a code the hook procedure uses to determine how to process the message. If  code is less than zero, the hook procedure must pass the message to the CallNextHookEx function without further processing and should return the value returned by  CallNextHookEx. This parameter can be one of the following values.
HC_ACTION
The  wParam and  lParam parameters contain information about a keystroke message.
HC_NOREMOVE
The  wParam and  lParam parameters contain information about a keystroke message, and the keystroke message has not been removed from the message queue. (An application called the PeekMessage function, specifying the  PM_NOREMOVE flag.)
wParam
[in] Specifies the virtual-key code of the key that generated the keystroke message.
lParam
[in] Specifies the repeat count, scan code, extended-key flag, context code, previous key-state flag, and transition-state flag. For more information about the  lParam parameter, see Keystroke Message Flags. This parameter can be one or more of the following values.
0-15
Specifies the repeat count. The value is the number of times the keystroke is repeated as a result of the user's holding down the key.
16-23
Specifies the scan code. The value depends on the OEM.
24
Specifies whether the key is an extended key, such as a function key or a key on the numeric keypad. The value is 1 if the key is an extended key; otherwise, it is 0.
25-28
Reserved.
29
Specifies the context code. The value is 1 if the ALT key is down; otherwise, it is 0.
30
Specifies the previous key state. The value is 1 if the key is down before the message is sent; it is 0 if the key is up.
31
Specifies the transition state. The value is 0 if the key is being pressed and 1 if it is being released.

Return Value

If code is less than zero, the hook procedure must return the value returned by CallNextHookEx.

If code is greater than or equal to zero, and the hook procedure did not process the message, it is highly recommended that you call CallNextHookEx and return the value it returns; otherwise, other applications that have installed WH_KEYBOARD hooks will not receive hook notifications and may behave incorrectly as a result. If the hook procedure processed the message, it may return a nonzero value to prevent the system from passing the message to the rest of the hook chain or the target window procedure.

Remarks

An application installs the hook procedure by specifying the WH_KEYBOARD hook type and a pointer to the hook procedure in a call to the SetWindowsHookEx function. 

keyboardProc鈎子過程是定義在應用程式中或是庫檔案中的回調函數,它有SetWindowsHookEx來使用。當應用程式中調用了GetMessage或是PeekMessage方法時并且處理的是鍵盤消息(WM_KEYUP或WM_KEYDOWN)HOOKPROC類型定義了一個指向回調函數的指針。從原型定義中我們可以看出:typedef LRESULT (CALLBACK* HOOKPROC)(int code, WPARAM wParam, LPARAM lParam);

它其實就是在WinUser.h中定義的一個函數指針

一個簡單的回調函數的例子如下(鍵盤鈎子為例)

// 鈎子回調函數(鈎子過程)
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode < 0 || nCode == HC_NOREMOVE)
		//向下傳遞消息使得hook鍊上的其他函數可以處理該消息
		return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);	
    if (lParam & 0x40000000)	// Check the previous key state
	{
		return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
	}
	//将自定義消息發送到主視窗
	//wParam 定義了虛拟鍵代碼
	//lParam 定義了按鍵資料
    ::PostMessage(g_hWnd, WM_KEYSTROKE, wParam, lParam);
	
    return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
           

上面PostMessage函數中的g_hWnd是主視窗的句柄,就是我們用來處理檢測到的消息的那個視窗。

WM_KEYSTROKE是我們自己定義的一個消息宏,當鈎子檢測到消息後,就将這個消息發送到主視窗來進行處理。

#define WM_KEYSTROKE (WM_USER + 101)

我們定義了下面一個函數來安裝或解除安裝一個消息鈎子。

//安裝或者解除安裝鈎子的函數
BOOL WINAPI SetKeyboardHook(BOOL bInstall, DWORD dwThreadId, HWND hWndCaller)
{
	BOOL bOk;
	g_hWnd = hWndCaller;
	
	if (bInstall)
	{
		g_hHook = ::SetWindowsHookEx(WH_KEYBOARD, KeyboardHookProc, 
			ModuleFromAddress(KeyboardHookProc), dwThreadId);//安裝消息鈎子
		bOk = (g_hHook != NULL);
	}
	else 
	{
		bOk = ::UnhookWindowsHookEx(g_hHook);
		g_hHook = NULL;
	}
	
	return bOk;
}
           

ModuleFromAddress函數幫助我們獲得子產品位址:

HMODULE WINAPI ModuleFromAddress(PVOID pv) 
{
	MEMORY_BASIC_INFORMATION mbi;
	if (::VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)
	{
		return (HMODULE)mbi.AllocationBase;
	}
	else
	{
		return NULL;
	}
}
           

由于上面我們定義的消息鈎子的回調函數是和安裝鈎子的程式代碼定義在一起的,而不是定義在外部DLL中,是以這個鈎子隻能監視本線程的消息,而不是一個全局消息鈎子。要想實作能對所有程式的鍵盤消息進行檢測,必須将消息鈎子的回調函數定義在DLL子產品中。

DLL中的定義如下:

.h

#ifdef HOOKDLL_EXPORTS
#define HOOKDLL_API __declspec(dllexport)
#else
#define HOOKDLL_API __declspec(dllimport)
#endif

//自定義消息,當鍵盤按鍵按下和釋放時發送消息
#define WM_KEYSTROKE (WM_USER + 101)
#define WM_KEYINPUT  (WM_USER + 102)

//導出函數
BOOL HOOKDLL_API WINAPI SetKeyboardHook(BOOL bInstall, 
										DWORD dwThreadId = 0, 
										HWND hWndCaller = NULL);
           

.cpp

#include <windows.h>

#define HOOKDLL_EXPORTS
#include "HookDll.h"


// Shared data among all instances.
#pragma data_seg(".HOOKDATA")
HWND g_hWnd = NULL;	        // Window handle
HHOOK g_hHook = NULL;		// Hook handle

// Get module from address
HMODULE WINAPI ModuleFromAddress(PVOID pv) 
{
	MEMORY_BASIC_INFORMATION mbi;
	if (::VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)
	{
		return (HMODULE)mbi.AllocationBase;
	}
	else
	{
		return NULL;
	}
}

// 鈎子回調函數(鈎子過程)
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode < 0 || nCode == HC_NOREMOVE)
		//向下傳遞消息使得hook鍊上的其他函數可以處理該消息
		return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);	
    if (lParam & 0x40000000)	// Check the previous key state
	{
		return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
	}
	//将自定義消息發送到主視窗
	//wParam 定義了虛拟鍵代碼
	//lParam 定義了按鍵資料
    ::PostMessage(g_hWnd, WM_KEYSTROKE, wParam, lParam);
	
    return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
//安裝或者解除安裝鈎子的函數
BOOL WINAPI SetKeyboardHook(BOOL bInstall, DWORD dwThreadId, HWND hWndCaller)
{
	BOOL bOk;
	g_hWnd = hWndCaller;
	
	if (bInstall)
	{
		g_hHook = ::SetWindowsHookEx(WH_KEYBOARD, KeyboardHookProc, 
			ModuleFromAddress(KeyboardHookProc), dwThreadId);//安裝消息鈎子
		bOk = (g_hHook != NULL);
	}
	else 
	{
		bOk = ::UnhookWindowsHookEx(g_hHook);
		g_hHook = NULL;
	}
	
	return bOk;
}
           

.def

LIBRARY	CppHooKDll
EXPORTS
   SetKeyboardHook
SECTIONS
	.HOOKDATA   Read Write Shared
           

在需要安裝全局鈎子的程式中調用:

::SetWindowsHookEx(WH_KEYBOARD, KeyboardHookProc, 
			ModuleFromAddress(KeyboardHookProc), 0)