我写的几篇技术文章之一:Windows消息拦截技术的应用
Windows消息攔截技術(shù)的應(yīng)用<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
民航合肥空管中心 周毅
?
一、前 言
眾所周知,Windows程式的運(yùn)行是依靠發(fā)生的事件來驅(qū)動。換句話說,程式不斷等待一個消息的發(fā)生,然后對這個消息的類型進(jìn)行判斷,再做適當(dāng)?shù)奶幚怼L幚硗甏舜蜗⒑笥只氐降却隣顟B(tài)。從上面對Windows程式運(yùn)行機(jī)制的分析不難發(fā)現(xiàn),消息在用戶與程式之間進(jìn)行交流時起了一種中間“語言”的作用。在程式中接收和處理消息的主角是窗口,它通過消息泵接收消息,再通過一個窗口過程對消息進(jìn)行相應(yīng)的處理。
消息攔截的實現(xiàn)是在窗口過程處理消息之前攔截到消息并做相關(guān)處理后再傳送給原窗口過程。通常情況下,程序員可以在窗口過程中處理接收到的消息,這就要求窗口過程必須在開發(fā)程序時完成,但是在一些應(yīng)用中常常需要獲取和處理另外應(yīng)用程序或其它單元模塊中的消息,實現(xiàn)此類功能的技術(shù)也就本文要討論的主題――消息攔截技術(shù)。
?
二、理解Windows消息機(jī)制
在深入探討消息攔截技術(shù)實現(xiàn)原理之前,讓我們先來溫習(xí)一下Windows消息機(jī)制原理知識。
1、? 消息的產(chǎn)生
消息作為程序與外界交流的“語言”,它的產(chǎn)生自然來自外界,但這里所說的外界,不只是簡單的指程序之外或軟件系統(tǒng)之外,而是泛指消息處理模塊之外的模塊、Windows系統(tǒng)、其它應(yīng)用程序以及硬件等。通常根據(jù)消息產(chǎn)生的方式將其分為兩大類,即硬件消息和軟件消息。硬件消息,常指由硬件裝置所產(chǎn)生的事件(如鼠標(biāo)或鍵盤被按下),放在系統(tǒng)消息隊列(System Queue)中,再由系統(tǒng)消息處理機(jī)構(gòu)將消息發(fā)送給應(yīng)用程序消息隊列中。軟件消息,常指由Windows系統(tǒng)或其它應(yīng)用程序發(fā)送的信息,它直接放入應(yīng)用程序消息隊列(Application Queue)中,再由應(yīng)用程序消息處理機(jī)構(gòu)將消息傳遞給相應(yīng)的窗口。
2、? 消息的組成
一個消息由一個消息名稱(UINT),和兩個參數(shù)(WPARAM,LPARAM)。當(dāng)用戶進(jìn)行了輸入或是窗口的狀態(tài)發(fā)生改變時系統(tǒng)都會發(fā)送消息到某一個窗口。例如當(dāng)菜單轉(zhuǎn)中之后會有WM_COMMAND消息發(fā)送,WPARAM的高字中(HIWORD(wParam))是命令的ID號,對菜單來講就是菜單ID。當(dāng)然用戶也可以定義自己的消息名稱,也可以利用自定義消息來發(fā)送通知和傳送數(shù)據(jù)。
3、? 消息的接收者
一個消息必須由一個窗口接收。在窗口過程(WNDPROC)中可以對消息進(jìn)行分析,對應(yīng)用程序要求處理的消息進(jìn)行相應(yīng)的處理工作,對于那么不需要應(yīng)用程序處理的消息可簡單的調(diào)用缺省處理。例如你希望對菜單選擇進(jìn)行處理那么你可以定義對WM_COMMAND進(jìn)行處理的代碼,如果希望在窗口中進(jìn)行圖形輸出就必須對WM_PAINT進(jìn)行處理。
4、? 消息的處理
窗口接收到發(fā)送給自己的消息后,將消息結(jié)構(gòu)作為參數(shù)調(diào)用窗口過程對消息進(jìn)行相應(yīng)的處理??梢詫⒋翱谶^程看作消息處理代碼的集合,窗口過程函數(shù)的原型為:
long FAR PASCAL WndProc(HWND hWnd,WORD message,WORD wParam,LONG lParam);
?? 其中,hWnd為窗口句柄,message為消息名稱,wParam,lParam為兩個參數(shù)。
?? 在Windows中,應(yīng)用程序不直接調(diào)用任何窗口函數(shù),而是等待Windows調(diào)用窗口函數(shù),請求完成任務(wù)或返回信息。為保證Windows調(diào)用這個窗口函數(shù),這個函數(shù)必須先向Windows登記,然后在Windows實施相應(yīng)操作時回調(diào),所以窗口函數(shù)又稱為回調(diào)函數(shù)。WndProc是一個主回調(diào)函數(shù),Windows至少有一個回調(diào)函數(shù)。它是在應(yīng)用程序進(jìn)行窗口類注冊時向Windows登記的。
?
三、利用鉤子(Hook)攔截消息
1、 何為鉤子(Hook)?
鉤子(Hook)機(jī)制允許應(yīng)用程序截獲處理window消息或特定事件。與DOS中斷截獲處理機(jī)制有類似之處。鉤子是Windows消息處理機(jī)制的一個平臺,應(yīng)用程序可以在上面設(shè)置子程以監(jiān)視指定窗口的某種消息,而且所監(jiān)視的窗口可以是其他進(jìn)程所創(chuàng)建的。當(dāng)消息到達(dá)后,鉤子可以在目標(biāo)窗口處理函數(shù)之前處理它并且可以阻止消息的傳遞。每一個鉤子都有一個與之相關(guān)聯(lián)的指針列表,稱之為鉤子鏈表,該鏈表中的指針指向這個鉤子的各個處理子程。鉤子的種類很多,每種鉤子可以攔截并處理相應(yīng)種類的消息。當(dāng)鉤子所監(jiān)視的消息出現(xiàn)時,Windows調(diào)用鏈表中的第一個鉤子子程,第一個過程完成后將消息傳遞鏈表中的下一個鉤子子程,直至鏈表中所有鉤子子程都執(zhí)行完成(注意:如果在其中有一個鉤子在執(zhí)行完成前不執(zhí)行消息傳遞,其后面的鉤子過程和原窗口過程都不會再接收到消息。)后將消息返回給窗口過程。
2、 鉤子子程函數(shù)
鉤子子程是一個應(yīng)用程序定義的回調(diào)函數(shù)。用以監(jiān)視系統(tǒng)或某一特定類型的事件,這些事件可以是與某一特定線程關(guān)聯(lián)的,也可以是系統(tǒng)中所有線程的事件。其函數(shù)原型為:
?
LRESULT?CALLBACK?HookProc ?(??int?nCode,?WPARAM?wParam,?LPARAM?lParam );
?
其中,nCode參數(shù)是Hook代碼,Hook子程使用這個參數(shù)來確定任務(wù)。這個參數(shù)的值依賴于Hook類型,每一種Hook都有自己的Hook代碼特征字符集。 Windows系統(tǒng)提供了多種類型的鉤子,每一種類型的Hook可以使應(yīng)用程序能夠監(jiān)視不同類型的系統(tǒng)消息處理機(jī)制。
wParam和lParam參數(shù)的值依賴于Hook代碼,但是它們的典型值是包含了關(guān)于發(fā)送或者接收消息的信息。
3、鉤子的安裝與卸載
鉤子的安裝是通過SDK API SetWindowsHookEx()來實現(xiàn)的,它將鉤子子程安裝到系統(tǒng)鉤子鏈表中。其函數(shù)原型
?
HHOOK?SetWindowsHookEx(?int?idHook,HOOKPROC?lpfn,HINSTANCE?hMod, DWORD?dwThreadId?);
?
其中,idHook是指鉤子的類型。表一中列出部分鉤子的類型及其說明。
(表一)
| 類型 | 說明 |
| WH_CALLWNDPROC | 系統(tǒng)在消息發(fā)送到接收窗口過程之前調(diào)用此子程 |
| WH_CALLWNDPROCRET?Hooks | 在窗口過程處理完消息之后調(diào)用此子程 |
| WH_GETMESSAGE | 監(jiān)視從GetMessage?/?PeekMessage函數(shù)返回的消息 |
| WH_KEYBOARD | 監(jiān)視輸入到消息隊列中的鍵盤消息 |
| WH_MOUSE | 監(jiān)視輸入到消息隊列中的鼠標(biāo)消息 |
| 限于篇幅,其它消息類型就不一一列出了。有關(guān)內(nèi)容可參見MSDN。 | |
?
lpfn是指鉤子子程的地址指針。如果dwThreadId參數(shù)為0?或是一個由別的進(jìn)程創(chuàng)建的線程的標(biāo)識,lpfn必 須指向DLL中的鉤子子程。除此以外,lpfn可以指向當(dāng)前進(jìn)程的一段鉤子子程代碼。?
?hMod是指應(yīng)用程序?qū)嵗木浔?。?biāo)識包含lpfn所指的子程的DLL。如果dwThreadId?標(biāo)識當(dāng)前進(jìn)程創(chuàng)建的一個線程,而且子程代碼位于當(dāng)前進(jìn)程,hMod必須為NULL。?
dwThreadId是指與安裝的鉤子子程相關(guān)聯(lián)的線程的標(biāo)識符,如果為0,鉤子子程與所有的線程關(guān)聯(lián)。
函數(shù)成功則返回鉤子的句柄,失敗返回NULL。
?
鉤子在使用完之后需要用UnHookWindowsHookEx()卸載,否則會造成麻煩。卸載鉤子比較簡單,UnHookWindowsHookEx()只有一個參數(shù)。函數(shù)原型如下:
UnHookWindowsHookEx??(?HHOOK?hhk??)
其中,參數(shù)hhk是SetWindowsHookEx()函數(shù)返回鉤子句柄,所以設(shè)計程序時一定要保存好這個句柄,以便卸載時使用。函數(shù)成功返回TRUE,否則返回FALSE。
?
4、系統(tǒng)鉤子與線程鉤子
Windows系統(tǒng)根據(jù)鉤子監(jiān)視事件的范圍將鉤子分為系統(tǒng)鉤子(全局鉤子)和線程鉤子(局部鉤子)兩種。由SetWindowsHookEx()函數(shù)的最后一個參數(shù)決定了此鉤子是系統(tǒng)鉤子還是線程鉤子。線程勾子用于監(jiān)視指定線程的事件消息。線程勾子一般在當(dāng)前線程或者當(dāng)前線程派生的線程內(nèi)。 系統(tǒng)勾子監(jiān)視系統(tǒng)中的所有線程的事件消息。因為系統(tǒng)勾子會影響系統(tǒng)中所有的應(yīng)用程序,所以勾子函數(shù)必須放在獨(dú)立的動態(tài)鏈接庫(DLL)?中。系統(tǒng)自動將包含"鉤子回調(diào)函數(shù)"的DLL映射到受鉤子函數(shù)影響的所有進(jìn)程的地址空間中,即將這個DLL注入了那些進(jìn)程。
?
5、? 鉤子的實現(xiàn)
本文的實例實現(xiàn)攔截記事本(NotePad.exe)程序的WM_CHAR消息的功能。如讀者想實現(xiàn)其它功能,可直接在鉤子子程函數(shù)中加入代碼。
(1)、選擇MFC?AppWizard(DLL)創(chuàng)建項目NotePadhook并選擇MFC?Extension?DLL(共享MFC拷貝)類型。
(2)、創(chuàng)建NotePadHook.h文件,在其中建立鉤子類:?
class?AFX_EXT_CLASS?CNotePadHook:public?CObject?
{
public:
CNotePadHook();?//鉤子類的構(gòu)造函數(shù)
~CNotePadHook();?//鉤子類的析構(gòu)函數(shù)
BOOL?StartHook(HWND?hWnd);? //安裝鉤子函數(shù)?
BOOL?StopHook();?卸載鉤子函數(shù)
};?
(3)、在NotePadHook.cpp中加入#include “NotePadHook.h”。
(4)、在NotePadHook.cpp中加入共享數(shù)據(jù)段:
#pragma data_seg("sharedata")? //共享數(shù)據(jù)段,段內(nèi)的變量可被鉤子所有實例共享。
HHOOK glhHook=NULL;? //鉤子句柄。
HINSTANCE glhInstance=NULL;? //DLL實例句柄。
#pragma data_seg()?
(5)、僅定義一個數(shù)據(jù)段還不能達(dá)到共享數(shù)據(jù)的目的,還要告訴編譯器該段的屬性。要在.DEF文件中設(shè)置段的屬性,打開.DEF文件加入如下代碼:
SETCTIONS?
sharedata?READ?WRITE?SHARED?
(6)、在主文件NotePadHook.cpp的DllMain函數(shù)中加入保存DLL實例句柄:
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
????? //如果使用lpReserved參數(shù)則刪除下面這行?
????? UNREFERENCED_PARAMETER(lpReserved);
?
????? if (dwReason == DLL_PROCESS_ATTACH)
????? {
????? ????? TRACE0("NOtePadHOOK.DLL Initializing!\n");
????? ????? ?//擴(kuò)展DLL僅初始化一次?
?????????? if (!AfxInitExtensionModule(NotePadHookDLL, hInstance))
?????????? ????? return 0;
????? ????? new CDynLinkLibrary(NotePadHookDLL);
//把DLL加入動態(tài)MFC類庫中?
????? ????? glhInstance = hInstance;
?????? //插入保存DLL實例句柄?
????? }
????? else if (dwReason == DLL_PROCESS_DETACH)
????? {
????? ????? TRACE0("NotePadHOOK.DLL Terminating!\n");
?????????? //終止這個鏈接庫前調(diào)用它?
????? ????? AfxTermExtensionModule(NotePadHookDLL);
????? }
????? return 1;??
}
(7)、類CNotePadHook的成員函數(shù)的具體實現(xiàn):
CNotePadHook::CNotePadHook(){}
CNotePadHook::~CNotePadHook(){ StopHook(); }
BOOL CNotePadHook::StartHook(HWND hWnd) //安裝鉤子。
{
? BOOL bResult=FALSE;
glhHook=SetWindowsHookEx(WH_CALLWNDPROC,NotePadProc,glhInstance,0);
? if(glhHook!=NULL) bResult=TRUE;
return bResult;
}
CNotePadHook::StopHook()
{
?? BOOL bResult = FALSE;
?? if(glhHook){
????? bResult=UnhookWindowsHookEx(glhHook);
?? if(bResult)? glhHook=NULL;
?? return bResult;
}
(8)、鉤子子程的實現(xiàn):
LRESULT?WINAPI?NotePadProc(int?nCode,WPARAM?wparam,LPARAM?lparam)?
{
? PCWPSTRUCT pcs = NULL;
pcs = (PCWPSTRUCT)lParam;
????? if( pcs && pcs->hwnd!=NULL )
????? {
?????????? ? TCHAR szClass[256];
?????????? ? GetClassName(pcs->hwnd ,szClass,255);//獲得攔截的窗口類名。
?????????? ? if( strcmp(szClass,"Notepad")==0)
?????????? ? {
?????????? ???? if( pcs->message == WM_CHAR )
?????????? ????? ?{
?????????? ????? ??? AfxMessageBox("HOOK NOTEPAD WM_CHAR OK!!!");
?????????? ????? ?}
????? ????? ? }
}
??? return CallNextHookEx(glhHook,nCode,wParam,lParam);//繼續(xù)傳遞消息。
}
?
(9)、編譯項目生成NotePadHook.dll。
?雖然已經(jīng)完成了鉤子類,但還不能實現(xiàn)鉤子功能。我們必須寫一個程序來啟動鉤子,將鉤子DLL注入其它程序的內(nèi)存空間并將鉤子加入到系統(tǒng)鉤子鏈表中。由于限于篇幅,在本文就不具體講述鉤子啟動程序的實例,只將編寫啟動程序應(yīng)注意的事項說明如下:
(1)、將NotePadHook項目中Debug\NotePadHook.lib加入到項目設(shè)置鏈接標(biāo)簽中。
(2)、將NotePadHook項目中NotePadHook.h文件include到stdafx.h。
(3)、首先需要創(chuàng)建一個CNotePadHook類實例,啟動鉤子時調(diào)用類成員StartHook(),卸載鉤子時調(diào)用類成員StopHook()。
?
四、利用窗口子類化(SubClass)攔截消息
前面已提及,每個窗口都有一個在它的窗口類中定義的窗口過程。該窗口過程處理每個發(fā)送到窗口的消息。如果想自己編寫窗口過程,修改它的行為是沒有問題的。但是,如果該窗口過程屬于別人,則將沒有源代碼進(jìn)行修改。例如,應(yīng)用程序中的每個按鈕,都是由系統(tǒng)提供的BUTTON窗口創(chuàng)建的,它有完全屬于自己的窗口過程。如果想改變該窗口的外觀,則不能通過改變它的WM_PAINT處理函數(shù)來實現(xiàn),因為它是不可得的。那么,怎樣能改變這些按鈕的外觀,而無需重新編寫原來的控件呢?只要用自己的窗口過程的地址,替換窗口對象的初始窗口過程的地址即可。這種技術(shù)也是本節(jié)討論的主題 – 窗口子類化技術(shù)。
1、窗口子類化原理
應(yīng)用程序在創(chuàng)建一個新窗口之前要向Windows系統(tǒng)注冊這個窗口的類,首先要填寫一個WNDCLASS結(jié)構(gòu),其中的結(jié)構(gòu)參數(shù)lpfnWndProc就是該類窗口函數(shù)的地址,接著調(diào)用RegisterClass()函數(shù)向Windows系統(tǒng)申請注冊這個窗口類。這時Windows會為其分配一塊內(nèi)存來存放該類的全部信息,這個內(nèi)存塊稱為窗口類內(nèi)存塊。
窗口子類化技術(shù)實際上就是改變窗口內(nèi)存塊中的有關(guān)參數(shù)。由于這種修改只涉及到一個窗口的窗口內(nèi)存塊,因此它不會影響到屬于同一窗口類的其它窗口的功能和表現(xiàn)。窗口子類化中最常見的是修改窗口內(nèi)存塊中的窗口函數(shù)地址(lpfnWndProc),使其指向一個新的窗口函數(shù),從而改變原窗口函數(shù)的處理方法,以達(dá)到修改其窗口過程的目的。
2、窗口子類化的實現(xiàn)
窗口子類化實現(xiàn)的核心是改變窗口過程的地址,可以通過SDK API提供的幾個函數(shù)來實現(xiàn)。具體步驟如下:
a.編寫子類化窗口過程函數(shù)。該函數(shù)必須為標(biāo)準(zhǔn)的窗口過程函數(shù)格式即:?
LRESULT?CALLBACK?SubClassWndProc?(?HWND?,?UINT?,?WPARAM?,?LPARAM?)?;?
??? 此函數(shù)的參數(shù)意義與前面講述的窗口過程函數(shù)參數(shù)類似。
b.調(diào)用GetWindowLong?(?hWnd?,?GWL_WNDPROC?)?函數(shù)獲得原窗口函數(shù)的地址并保存起來;其中參數(shù)hWnd為待子類化窗口句柄。
C.調(diào)用SetWIndowLong?(?hWnd?,?GWL_WNDPROC?,?SubClassWndProc?)?把窗口函數(shù)設(shè)置成子類化窗口函數(shù),完成窗口子類化。?
?
為了減少子類化過程中繁瑣的工作,MFC中提供了對子類化的支持,它簡化了子類化過程,利用CWnd類SubClassWindows()函數(shù)來實現(xiàn)子類化。為了讓讀者對子類化過程有一個直觀的認(rèn)識,下面將利用MFC實現(xiàn)對一個編輯(Edit)控件的子類化。
(1)、創(chuàng)建一個從MFC控件類CEdit派生的新控件類CSubEdit。
(2)、添加CSubEdit::PreTranslateMessage(MSG* pMsg)
BOOL CSubEdit::PreTranslateMessage(MSG* pMsg)
{
?? if( pMsg->message==WM_KEYDOWN&&pMsg->wParam==VK_RETURN)
?? {
? //當(dāng)在Edit控件上按下回車鍵后…
?…..
//限于篇幅處理內(nèi)容略。
return TRUE;
}
CEdit::PreTranslateMessage(pMsg);
}
(3)、在包含此控件的對話框類頭文件中控件變量類型從CEdit改為CSubEdit。
(4)、在包含此控件的對話框類文件中對Edit控件進(jìn)行子類化,代碼如下:
HWND HwND;
? GetDlgItem(IDC_SUB_EDIT,&hWnd);//其中IDC_SUB_EDIT是控件ID。
m_subEdit.SubclassWindow(hWnd); //m_subEdit為控件變量名。
?
五、小結(jié)
本文討論了實現(xiàn)消息攔截的兩種方法,其中鉤子技術(shù)用途廣泛,不僅可以實現(xiàn)對同進(jìn)程內(nèi)消息的攔截,而且還可以實現(xiàn)對另外進(jìn)程消息的攔截。而子類化技術(shù)主要用于實現(xiàn)對同一進(jìn)程單元模塊中的窗口消息的攔截。程序員可以根據(jù)實際應(yīng)用需求選擇其一來實現(xiàn)消息的擋截。轉(zhuǎn)載于:https://www.cnblogs.com/biqing/archive/2004/05/20/10571.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的我写的几篇技术文章之一:Windows消息拦截技术的应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自动化测试简介及环境搭建
- 下一篇: 什么事接口