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