對于如何使用和創(chuàng)建鉤子有許多的爭議,這篇文章試圖澄清這些問題。
注意:如果你只是在自己的進(jìn)程內(nèi)使用鉤子則不會有下面的問題, 這只發(fā)生在你使用系統(tǒng)鉤子的時候。
關(guān)鍵問題在于 地址空間,DLL函數(shù)中的代碼所創(chuàng)建的任何對象(包括變量)都?xì)w調(diào)用它的線程或進(jìn)程所有。當(dāng)進(jìn)程在載入DLL時,操作系統(tǒng)自動把DLL地址映射到該進(jìn)程的私有空間,也就是進(jìn)程的虛擬地址空間,而且也復(fù)制該DLL的全局?jǐn)?shù)據(jù)的一份拷貝到該進(jìn)程空間。也就是說每個進(jìn)程所擁有的相同的DLL的全局?jǐn)?shù)據(jù),是私有的,DLL成為進(jìn)程的一部分,以這個進(jìn)程的身份執(zhí)行,使用這個進(jìn)程的堆棧。這意味著數(shù)據(jù)會被重新初始化。典型地,它們將是零。
有人建議在DLL上存放數(shù)據(jù)的地址。 這是不可能的。有人反對? 那好,這不是不可能的,但這是不可能有什么 用途 的。既使你創(chuàng)建的是對DLL 的所有實(shí)例可見的共享內(nèi)存變量,這一變量只有在儲存它的進(jìn)程中才有實(shí)際的意義。 對于所有其它的進(jìn)程,這僅僅是一串比特位,并且如果你設(shè)法使用它作為地址,對于事件被攔截的進(jìn)程而言,這個地址是完全無用甚至導(dǎo)致程序崩潰。
這個分開的地址空間的概念是一個難以掌握的概念。 讓我使用圖片說明它。
讓我們看一看在進(jìn)程B會發(fā)生什么 。 當(dāng)事件在進(jìn)程中B中被鉤時,DLL 被映射。代碼被遷入到進(jìn)程中B中另外的一個地址。如果你調(diào)試進(jìn)程中B ,留意在共有的區(qū)域中的 &something,你會發(fā)現(xiàn) &something 的地址是不同的,但 &something 的內(nèi)容會是同樣的; 在你的進(jìn)程中或進(jìn)程A中對&something的內(nèi)容做的改變立刻就能在進(jìn)程B中看見,即使進(jìn)程B是在另外的一個地址(虛擬地址)看見的。(這是在同樣的物理內(nèi)存地點(diǎn))。當(dāng)我提到巧合時,"巧合" 是指被策劃; Windows總是試圖將DLL映射入同樣的虛擬地址, 它試圖這么干,但它很少成功。
這就意味著,如果你在DLL中存放了一個指向回調(diào)函數(shù)的指針,但在實(shí)際運(yùn)行進(jìn)程A 或進(jìn)程B時,它可能會指向別的地址。這也意味著你將不能在DLL中使用MFC--它不能是一個擴(kuò)展MFC DLL或MFC DLL,因?yàn)檫@些DLL(動態(tài)鏈接庫)會調(diào)用MFC 函數(shù)。
那么MFC 函數(shù)在哪里? 他們是在你的地址空間, 而不是在進(jìn)程A或進(jìn)程B的 地址空間! 因?yàn)樗麄兛赡苁怯?/span>Visual.basic ,Java或其他語言寫的 , 所以你必須寫straight-C DLL ,并且我建議你忽略整個C runtime library.,只使用API 。 用lstrcpy 代替 strcpy 或 tcscpy,用 lstrcmp 代替 strcmp 或 tcscmp,等等。
如何讓你的DLL與其controlling server 通信?
一種解答將使用 ::PostMessage 或 ::SendMessage 函數(shù)。(我這里提到的是原始API 的調(diào)用,不是MFC 的調(diào)用!) 每當(dāng)可能使用 ::PostMessage時,盡可能使用它優(yōu)先于使用 ::SendMessage。否則,如果你的進(jìn)程不幸停止,因?yàn)榇蠹叶急蛔钄r在一個永遠(yuǎn)不會返回的::SendMessage,其他進(jìn)程也將停止,然后是整個系統(tǒng)都停止。
你也可以考慮在共享內(nèi)存區(qū)域使用信息隊列,但那個題目在這篇文章范圍之外。
在 ::SendMessage 或 ::PostMessage中,你無法傳回一個指針 (我們將忽略傳回一個相對指針進(jìn)入共享內(nèi)存區(qū)域的問題; 那也是在這篇文章范圍之外). 這是因?yàn)槟隳苁褂玫娜我恢羔樦甘镜牡刂芬词窃?/span>DLL 中, 要么是在在被鉤的進(jìn)程中。(進(jìn)程A 或進(jìn)程B) 因此在你的進(jìn)程中,這個指針是完全無用的。 你只能通過在 WPARAM 或 LPARAM中的信息傳回地址空間。
I 我強(qiáng)烈 建議為此使用登記的窗口消息。 你能發(fā)送消息到 MESSAGE_MAP(消息映射) 窗口,并在此使用 ON_REGISTERED_MESSAGE 宏指令。
現(xiàn)在關(guān)鍵是要得到 那個窗口的HWND(句柄)。 幸運(yùn)的是,這很容易。
你必須做的第一件事是創(chuàng)建共有的數(shù)據(jù)段。 所以我們使用 # pragma data_seg 聲明。 使用某一好記的數(shù)據(jù)段名字(它必須是沒有比8 個字符長) 。我想強(qiáng)調(diào)名字是任意的,這里使用了我自己的名字。 我發(fā)現(xiàn)如果我使用好的名字象 # pragma data_seg(".JOE") HANDLE hWnd = NULL; # pragma dta_seg() # pragma comment(linker ,"/ section:.JOE,rws ") # pragma 聲明一個數(shù)據(jù)段,在此范圍內(nèi)聲明的變量在初始化后將被指派到該數(shù)據(jù)段, 假設(shè)他們初始化. 如未初始化,變量將被分配到缺省數(shù)據(jù)段,而# pragma 不起作用。 初看起來, 這將阻止你在共有的數(shù)據(jù)段使用一些C++ 對象,因?yàn)槟銦o法初始化C++中用戶定義的對象。 這看來是一個根本局限。 # pragma comment 使連接器有命令行開關(guān)被顯示增加到鏈接步驟。 你可以進(jìn)入VC++ 項(xiàng)目| 設(shè)置 并且改變連接器命令行。 你可以預(yù)定某一機(jī)制設(shè)置窗口句柄,例如 void SetWindow(HWND w) {hWnd = w; } 但更經(jīng)常的是如下所示的與鉤子結(jié)合。 函數(shù) setMyHook 并且 clearMyHook 必須在此被聲明。這在我的另一文章中有詳細(xì)論述。“The Ultimate DLL Header File.” } // SetMyHook 在頭文件中,將下面的增加到類的protected段: 在application 文件中, 增加以下代碼到文件前部。 在 //{AFX_MSG comments: In your application file, add the following function: 你可以下載這個項(xiàng)目并建立它。 真正的關(guān)鍵是DLL 子工程項(xiàng)目; 其他的都不過是陪襯。有幾個其它的技術(shù)被用在這個例子里,包括各種各樣的圖畫技術(shù), ClipCursor 和 SetCapture的用法,區(qū)域選擇、屏幕更新等等。,因此除了展示鉤子函數(shù)的使用以外,對初級程序員掌握窗口樣式設(shè)計編程也有一些價值。.SHARE
或.SHR
或.SHRDATA
,別人會認(rèn)為名字有特殊的意義。 但是,我要說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 declaration
static 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;
}