對于如何使用和創(chuàng )建鉤子有許多的爭議,這篇文章試圖澄清這些問(wèn)題。
注意:如果你只是在自己的進(jìn)程內使用鉤子則不會(huì )有下面的問(wèn)題, 這只發(fā)生在你使用系統鉤子的時(shí)候。
關(guān)鍵問(wèn)題在于 地址空間,DLL函數中的代碼所創(chuàng )建的任何對象(包括變量)都歸調用它的線(xiàn)程或進(jìn)程所有。當進(jìn)程在載入DLL時(shí),操作系統自動(dòng)把DLL地址映射到該進(jìn)程的私有空間,也就是進(jìn)程的虛擬地址空間,而且也復制該DLL的全局數據的一份拷貝到該進(jìn)程空間。也就是說(shuō)每個(gè)進(jìn)程所擁有的相同的DLL的全局數據,是私有的,DLL成為進(jìn)程的一部分,以這個(gè)進(jìn)程的身份執行,使用這個(gè)進(jìn)程的堆棧。這意味著(zhù)數據會(huì )被重新初始化。典型地,它們將是零。
有人建議在DLL上存放數據的地址。 這是不可能的。有人反對? 那好,這不是不可能的,但這是不可能有什么 用途 的。既使你創(chuàng )建的是對DLL 的所有實(shí)例可見(jiàn)的共享內存變量,這一變量只有在儲存它的進(jìn)程中才有實(shí)際的意義。 對于所有其它的進(jìn)程,這僅僅是一串比特位,并且如果你設法使用它作為地址,對于事件被攔截的進(jìn)程而言,這個(gè)地址是完全無(wú)用甚至導致程序崩潰。
這個(gè)分開(kāi)的地址空間的概念是一個(gè)難以掌握的概念。 讓我使用圖片說(shuō)明它。

讓我們看一看在進(jìn)程B會(huì )發(fā)生什么 。 當事件在進(jìn)程中B中被鉤時(shí),DLL 被映射。代碼被遷入到進(jìn)程中B中另外的一個(gè)地址。如果你調試進(jìn)程中B ,留意在共有的區域中的 &something,你會(huì )發(fā)現 &something 的地址是不同的,但 &something 的內容會(huì )是同樣的; 在你的進(jìn)程中或進(jìn)程A中對&something的內容做的改變立刻就能在進(jìn)程B中看見(jiàn),即使進(jìn)程B是在另外的一個(gè)地址(虛擬地址)看見(jiàn)的。(這是在同樣的物理內存地點(diǎn))。當我提到巧合時(shí),"巧合" 是指被策劃; Windows總是試圖將DLL映射入同樣的虛擬地址, 它試圖這么干,但它很少成功。
這就意味著(zhù),如果你在DLL中存放了一個(gè)指向回調函數的指針,但在實(shí)際運行進(jìn)程A 或進(jìn)程B時(shí),它可能會(huì )指向別的地址。這也意味著(zhù)你將不能在DLL中使用MFC--它不能是一個(gè)擴展MFC DLL或MFC DLL,因為這些DLL(動(dòng)態(tài)鏈接庫)會(huì )調用MFC 函數。
那么MFC 函數在哪里? 他們是在你的地址空間, 而不是在進(jìn)程A或進(jìn)程B的 地址空間! 因為他們可能是用Visual.basic ,Java或其他語(yǔ)言寫(xiě)的 , 所以你必須寫(xiě)straight-C DLL ,并且我建議你忽略整個(gè)C runtime library.,只使用API 。 用lstrcpy 代替 strcpy 或 tcscpy,用 lstrcmp 代替 strcmp 或 tcscmp,等等。
如何讓你的DLL與其controlling server 通信?
一種解答將使用 ::PostMessage 或 ::SendMessage 函數。(我這里提到的是原始API 的調用,不是MFC 的調用!) 每當可能使用 ::PostMessage時(shí),盡可能使用它優(yōu)先于使用 ::SendMessage。否則,如果你的進(jìn)程不幸停止,因為大家都被阻攔在一個(gè)永遠不會(huì )返回的::SendMessage,其他進(jìn)程也將停止,然后是整個(gè)系統都停止。
你也可以考慮在共享內存區域使用信息隊列,但那個(gè)題目在這篇文章范圍之外。
在 ::SendMessage 或 ::PostMessage中,你無(wú)法傳回一個(gè)指針 (我們將忽略傳回一個(gè)相對指針進(jìn)入共享內存區域的問(wèn)題; 那也是在這篇文章范圍之外). 這是因為你能使用的任一指針指示的地址要么是在DLL 中, 要么是在在被鉤的進(jìn)程中。(進(jìn)程A 或進(jìn)程B) 因此在你的進(jìn)程中,這個(gè)指針是完全無(wú)用的。 你只能通過(guò)在 WPARAM 或 LPARAM中的信息傳回地址空間。
I 我強烈 建議為此使用登記的窗口消息。 你能發(fā)送消息到 MESSAGE_MAP(消息映射) 窗口,并在此使用 ON_REGISTERED_MESSAGE 宏指令。
現在關(guān)鍵是要得到 那個(gè)窗口的HWND(句柄)。 幸運的是,這很容易。
你必須做的第一件事是創(chuàng )建共有的數據段。 所以我們使用 # pragma data_seg 聲明。 使用某一好記的數據段名字(它必須是沒(méi)有比8 個(gè)字符長(cháng)) 。我想強調名字是任意的,這里使用了我自己的名字。 我發(fā)現如果我使用好的名字象 # pragma data_seg(".JOE") HANDLE hWnd = NULL; # pragma dta_seg() # pragma comment(linker ,"/ section:.JOE,rws ") # pragma 聲明一個(gè)數據段,在此范圍內聲明的變量在初始化后將被指派到該數據段, 假設他們初始化. 如未初始化,變量將被分配到缺省數據段,而# pragma 不起作用。 初看起來(lái), 這將阻止你在共有的數據段使用一些C++ 對象,因為你無(wú)法初始化C++中用戶(hù)定義的對象。 這看來(lái)是一個(gè)根本局限。 # pragma comment 使連接器有命令行開(kāi)關(guān)被顯示增加到鏈接步驟。 你可以進(jìn)入VC++ 項目| 設置 并且改變連接器命令行。 你可以預定某一機制設置窗口句柄,例如 void SetWindow(HWND w) {hWnd = w; } 但更經(jīng)常的是如下所示的與鉤子結合。 函數 setMyHook 并且 clearMyHook 必須在此被聲明。這在我的另一文章中有詳細論述。“The Ultimate DLL Header File.” } // SetMyHook 在頭文件中,將下面的增加到類(lèi)的protected段: 在application 文件中, 增加以下代碼到文件前部。 在 //{AFX_MSG comments: In your application file, add the following function: 你可以下載這個(gè)項目并建立它。 真正的關(guān)鍵是DLL 子工程項目; 其他的都不過(guò)是陪襯。有幾個(gè)其它的技術(shù)被用在這個(gè)例子里,包括各種各樣的圖畫(huà)技術(shù), ClipCursor 和 SetCapture的用法,區域選擇、屏幕更新等等。,因此除了展示鉤子函數的使用以外,對初級程序員掌握窗口樣式設計編程也有一些價(jià)值。.SHARE 或.SHR 或.SHRDATA,別人會(huì )認為名字有特殊的意義。 但是,我要說(shuō)NO。Sample: A Mouse Hook
header file (myhook.h)
#define UWM_MOUSEHOOK_MSG \ _T("UMW_MOUSEHOOK-" \ "{B30856F0-D3DD-11d4-A00B-006067718D04}")source file (myhook.cpp)
#include "stdafx.h"#include "myhook.h"#pragma data_seg(".JOE")HWND hWndServer = NULL;#pragma data_seg()#pragma comment("linker, /section:.JOE,rws")HINSTANCE hInstance;UINT HWM_MOUSEHOOK;HHOOK hook;// Forward declarationstatic LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam);
/***************************************************************** DllMain* Inputs:* HINSTANCE hInst: Instance handle for the DLL* DWORD Reason: Reason for call* LPVOID reserved: ignored* Result: BOOL* TRUE if successful* FALSE if there was an error (never returned)* Effect:* Initializes the DLL.****************************************************************/BOOL DllMain(HINSTANCE hInst, DWORD Reason, LPVOID reserved){ switch(Reason)
{ /* reason */
//**********************************************
// PROCESS_ATTACH
//**********************************************
case DLL_PROCESS_ATTACH:
// Save the instance handle because we need it to set the hook later
hInstance = hInst; // This code initializes the hook notification message
UWM_MOUSEHOOK = RegisterWindowMessage(UWM_MOUSEHOOK_MSG); return TRUE;
//**********************************************
// PROCESS_DETACH
//**********************************************
case DLL_PROCESS_DETACH:
// If the server has not unhooked the hook, unhook it as we unload
if(hWndServer != NULL)
clearMyHook(hWndServer); return TRUE;
} /* reason */
/***************************************************************** setMyHook* Inputs:* HWND hWnd: Window whose hook is to be set* Result: BOOL* TRUE if the hook is properly set* FALSE if there was an error, such as the hook already * being set* Effect:* Sets the hook for the specified window.* This sets a message-intercept hook (WH_GETMESSAGE)* If the setting is successful, the hWnd is set as the* server window.****************************************************************/__declspec(dllexport) BOOL WINAPI setMyHook(HWND hWnd) { if(hWndServer != NULL)
return FALSE;
hook = SetWindowsHookEx( WH_GETMESSAGE, (HOOKPROC)msghook, hInstance, 0);
if(hook != NULL)
{ /* success */
hWndServer = hWnd; return TRUE;
} /* success */
return FALSE;
/***************************************************************** clearMyHook* Inputs:* HWND hWnd: Window whose hook is to be cleared* Result: BOOL* TRUE if the hook is properly unhooked* FALSE if you gave the wrong parameter* Effect:* Removes the hook that has been set.****************************************************************/__declspec(dllexport) BOOL clearMyHook(HWND hWnd) { if(hWnd != hWndServer)
return FALSE;
BOOL unhooked = UnhookWindowsHookEx(hook); if(unhooked)
hWndServer = NULL; return unhooked;
}/***************************************************************** msghook* Inputs:* int nCode: Code value* WPARAM wParam: parameter* LPARAM lParam: parameter* Result: LRESULT** Effect:* If the message is a mouse-move message, posts it back to* the server window with the mouse coordinates* Notes:* This must be a CALLBACK function or it will not work!****************************************************************/static LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam)
{ // If the value of nCode is < 0, just pass it on and return 0
// this is required by the specification of hook handlers
if(nCode < 0)
{ /* pass it on */
CallNextHookEx(hook, nCode, wParam, lParam); return 0;
} /* pass it on */
// Read the documentation to discover what WPARAM and LPARAM
// mean. For a WH_MESSAGE hook, LPARAM is specified as being
// a pointer to a MSG structure, so the code below makes that
// structure available
LPMSG msg = (LPMSG)lParam; // If it is a mouse-move message, either in the client area or
// the non-client area, we want to notify the parent that it has
// occurred. Note the use of PostMessage instead of SendMessage
if(msg->message == WM_MOUSEMOVE ||
msg->message == WM_NCMOUSEMOVE) PostMessage(hWndServer, UWM_MOUSEMOVE, 0, 0);
// Pass the message on to the next hook
return CallNextHookEx(hook, nCode,
wParam, lParam); } // msghook
The server application
afx_msg LRESULT OnMyMouseMove(WPARAM,LPARAM);UINT UWM_MOUSEMOVE = ::RegisterWindowMessage(UWM_MOUSEMOVE_MSG);MESSAGE_MAP, 增加以下代碼ON_REGISTERED_MESSAGE(UWM_MOUSEMOVE, OnMyMouseMove)LRESULT CMyClass::OnMyMouseMove(WPARAM, LPARAM) { // ...do stuff here
return 0;
}

聯(lián)系客服