1、前 言程序員
衆所周知,Windows程式的運行是依靠發生的事件來驅動。換句話說,程式不斷等待一個消息的發生,而後對這個消息的類型進行判斷,再作適當的處理。處理完這次消息後又回到等待狀態。從上面對Windows程式運行機制的分析不難發現,消息在用戶與程式之間進行交流時起了一種中間「語言」的做用。在程式中接收和處理消息的主角是窗口,它經過消息泵接收消息,再經過一個窗口過程對消息進行相應的處理。web
消息攔截的實現是在窗口過程處理消息以前攔截到消息並作相關處理後再傳送給原窗口過程。一般狀況下,程序員能夠在窗口過程當中處理接收到的消息,這就要求窗口過程必須在開發程序時完成,可是在一些應用中經常須要獲取和處理另外應用程序或其它單元模塊中的消息,實現此類功能的技術也就本文要討論的主題――消息攔截技術。ide
2、理解Windows消息機制函數
在深刻探討消息攔截技術實現原理以前,讓咱們先來溫習一下Windows消息機制原理知識。spa
一、
消息做爲程序與外界交流的「語言」,它的產生天然來自外界,但這裏所說的外界,不僅是簡單的指程序以外或軟件系統以外,而是泛指消息處理模塊以外的模塊、Windows系統、其它應用程序以及硬件等。一般根據消息產生的方式將其分爲兩大類,即硬件消息和軟件消息。硬件消息,常指由硬件裝置所產生的事件(如鼠標或鍵盤被按下),放在系統消息隊列(System Queue)中,再由系統消息處理機構將消息發送給應用程序消息隊列中。軟件消息,常指由Windows系統或其它應用程序發送的信息,它直接放入應用程序消息隊列(Application Queue)中,再由應用程序消息處理機構將消息傳遞給相應的窗口。設計
二、
一個消息由一個消息名稱(UINT),和兩個參數(WPARAM,LPARAM)。當用戶進行了輸入或是窗口的狀態發生改變時系統都會發送消息到某一個窗口。例如當菜單轉中以後會有WM_COMMAND消息發送,WPARAM的高字中(HIWORD(wParam))是命令的ID號,對菜單來說就是菜單ID。固然用戶也能夠定義本身的消息名稱,也能夠利用自定義消息來發送通知和傳送數據。orm
三、
一個消息必須由一個窗口接收。在窗口過程(WNDPROC)中能夠對消息進行分析,對應用程序要求處理的消息進行相應的處理工做,對於那麼不須要應用程序處理的消息可簡單的調用缺省處理。例如你但願對菜單選擇進行處理那麼你能夠定義對WM_COMMAND進行處理的代碼,若是但願在窗口中進行圖形輸出就必須對WM_PAINT進行處理。
四、
窗口接收到發送給本身的消息後,將消息結構做爲參數調用窗口過程對消息進行相應的處理。能夠將窗口過程看做消息處理代碼的集合,窗口過程函數的原型爲:
long FAR PASCAL WndProc(HWND hWnd,WORD message,WORD wParam,LONG lParam);
3、利用鉤子(Hook)攔截消息
一、 何爲鉤子(Hook)?
鉤子(Hook)機制容許應用程序截獲處理window消息或特定事件。與DOS中斷截獲處理機制有相似之處。鉤子是Windows消息處理機制的一個平臺,應用程序能夠在上面設置子程以監視指定窗口的某種消息,並且所監視的窗口能夠是其餘進程所建立的。當消息到達後,鉤子能夠在目標窗口處理函數以前處理它而且能夠阻止消息的傳遞。每個鉤子都有一個與之相關聯的指針列表,稱之爲鉤子鏈表,該鏈表中的指針指向這個鉤子的各個處理子程。鉤子的種類不少,每種鉤子能夠攔截並處理相應種類的消息。當鉤子所監視的消息出現時,Windows調用鏈表中的第一個鉤子子程,第一個過程完成後將消息傳遞鏈表中的下一個鉤子子程,直至鏈表中全部鉤子子程都執行完成(注意:若是在其中有一個鉤子在執行完成前不執行消息傳遞,其後面的鉤子過程和原窗口過程都不會再接收到消息。)後將消息返回給窗口過程。
二、 鉤子子程函數
鉤子子程是一個應用程序定義的回調函數。用以監視系統或某一特定類型的事件,這些事件能夠是與某一特定線程關聯的,也能夠是系統中全部線程的事件。其函數原型爲:
LRESULT CALLBACK HookProc
其中,nCode參數是Hook代碼,Hook子程使用這個參數來肯定任務。這個參數的值依賴於Hook類型,每一種Hook都有本身的Hook代碼特徵字符集。 Windows系統提供了多種類型的鉤子,每一種類型的Hook可使應用程序可以監視不一樣類型的系統消息處理機制。
wParam和lParam參數的值依賴於Hook代碼,可是它們的典型值是包含了關於發送或者接收消息的信息。
三、鉤子的安裝與卸載
鉤子的安裝是經過SDK API SetWindowsHookEx()來實現的,它將鉤子子程安裝到系統鉤子鏈表中。其函數原型
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn,HINSTANCE hMod, DWORD dwThreadId );
其中,idHook是指鉤子的類型。表一中列出部分鉤子的類型及其說明。
(表一)
類型
WH_CALLWNDPROC
WH_CALLWNDPROCRET Hooks
WH_GETMESSAGE
WH_KEYBOARD
WH_MOUSE
限於篇幅,其它消息類型就不一一列出了。有關內容可參見MSDN。
lpfn是指鉤子子程的地址指針。若是dwThreadId參數爲0 或是一個由別的進程建立的線程的標識,lpfn必 須指向DLL中的鉤子子程。除此之外,lpfn能夠指向當前進程的一段鉤子子程代碼。
dwThreadId是指與安裝的鉤子子程相關聯的線程的標識符,若是爲0,鉤子子程與全部的線程關聯。
函數成功則返回鉤子的句柄,失敗返回NULL。
鉤子在使用完以後須要用UnHookWindowsHookEx()卸載,不然會形成麻煩。卸載鉤子比較簡單,UnHookWindowsHookEx()只有一個參數。函數原型以下:
UnHookWindowsHookEx
其中,參數hhk是SetWindowsHookEx()函數返回鉤子句柄,因此設計程序時必定要保存好這個句柄,以便卸載時使用。函數成功返回TRUE,不然返回FALSE。
四、系統鉤子與線程鉤子
Windows系統根據鉤子監視事件的範圍將鉤子分爲系統鉤子(全局鉤子)和線程鉤子(局部鉤子)兩種。由SetWindowsHookEx()函數的最後一個參數決定了此鉤子是系統鉤子仍是線程鉤子。線程勾子用於監視指定線程的事件消息。線程勾子通常在當前線程或者當前線程派生的線程內。 系統勾子監視系統中的全部線程的事件消息。由於系統勾子會影響系統中全部的應用程序,因此勾子函數必須放在獨立的動態連接庫(DLL) 中。系統自動將包含"鉤子回調函數"的DLL映射到受鉤子函數影響的全部進程的地址空間中,即將這個DLL注入了那些進程。
五、
本文的實例實現攔截記事本(NotePad.exe)程序的WM_CHAR消息的功能。如讀者想實現其它功能,可直接在鉤子子程函數中加入代碼。
(1)、選擇MFC AppWizard(DLL)建立項目NotePadhook並選擇MFC Extension DLL(共享MFC拷貝)類型。
(2)、建立NotePadHook.h文件,在其中創建鉤子類:
class AFX_EXT_CLASS CNotePadHook:public CObject
{
public:
CNotePadHook(); //鉤子類的構造函數
~CNotePadHook(); //鉤子類的析構函數
BOOL StartHook(HWND hWnd);
BOOL StopHook(); 卸載鉤子函數
};
(3)、在NotePadHook.cpp中加入#i nclude 「NotePadHook.h」。
(4)、在NotePadHook.cpp中加入共享數據段:
#pragma data_seg("sharedata")
HHOOK glhHook=NULL;
HINSTANCE glhInstance=NULL;
#pragma data_seg()
(5)、僅定義一個數據段還不能達到共享數據的目的,還要告訴編譯器該段的屬性。要在.DEF文件中設置段的屬性,打開.DEF文件加入以下代碼:
SETCTIONS
sharedata READ WRITE SHARED
(6)、在主文件NotePadHook.cpp的DllMain函數中加入保存DLL實例句柄:
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
//把DLL加入動態MFC類庫中
}
(7)、類CNotePadHook的成員函數的具體實現:
CNotePadHook::CNotePadHook(){}
CNotePadHook::~CNotePadHook(){ StopHook(); }
BOOL CNotePadHook::StartHook(HWND hWnd) //安裝鉤子。
{
glhHook=SetWindowsHookEx(WH_CALLWNDPROC,NotePadProc,glhInstance,0);
return bResult;
}
CNotePadHook::StopHook()
{
}
(8)、鉤子子程的實現:
LRESULT WINAPI NotePadProc(int nCode,WPARAM wparam,LPARAM lparam)
{
pcs = (PCWPSTRUCT)lParam;
}
}
(9)、編譯項目生成NotePadHook.dll。
(1)、將NotePadHook項目中Debug\NotePadHook.lib加入到項目設置連接標籤中。
(2)、將NotePadHook項目中NotePadHook.h文件include到stdafx.h。
(3)、首先須要建立一個CNotePadHook類實例,啓動鉤子時調用類成員StartHook(),卸載鉤子時調用類成員StopHook()。
4、利用窗口子類化(SubClass)攔截消息
前面已說起,每一個窗口都有一個在它的窗口類中定義的窗口過程。該窗口過程處理每一個發送到窗口的消息。若是想本身編寫窗口過程,修改它的行爲是沒有問題的。可是,若是該窗口過程屬於別人,則將沒有源代碼進行修改。例如,應用程序中的每一個按鈕,都是由系統提供的BUTTON窗口建立的,它有徹底屬於本身的窗口過程。若是想改變該窗口的外觀,則不能經過改變它的WM_PAINT處理函數來實現,由於它是不可得的。那麼,怎樣能改變這些按鈕的外觀,而無需從新編寫原來的控件呢?只要用本身的窗口過程的地址,替換窗口對象的初始窗口過程的地址便可。這種技術也是本節討論的主題 – 窗口子類化技術。
1、窗口子類化原理
應用程序在建立一個新窗口以前要向Windows系統註冊這個窗口的類,首先要填寫一個WNDCLASS結構,其中的結構參數lpfnWndProc就是該類窗口函數的地址,接着調用RegisterClass()函數向Windows系統申請註冊這個窗口類。這時Windows會爲其分配一塊內存來存放該類的所有信息,這個內存塊稱爲窗口類內存塊。
窗口子類化技術實際上就是改變窗口內存塊中的有關參數。因爲這種修改只涉及到一個窗口的窗口內存塊,所以它不會影響到屬於同一窗口類的其它窗口的功能和表現。窗口子類化中最多見的是修改窗口內存塊中的窗口函數地址(lpfnWndProc),使其指向一個新的窗口函數,從而改變原窗口函數的處理方法,以達到修改其窗口過程的目的。
2、窗口子類化的實現
窗口子類化實現的核心是改變窗口過程的地址,能夠經過SDK API提供的幾個函數來實現。具體步驟以下:
a.編寫子類化窗口過程函數。該函數必須爲標準的窗口過程函數格式即:
LRESULT CALLBACK SubClassWndProc ( HWND , UINT , WPARAM , LPARAM ) ;
b.調用GetWindowLong ( hWnd , GWL_WNDPROC ) 函數得到原窗口函數的地址並保存起來;其中參數hWnd爲待子類化窗口句柄。
C.調用SetWIndowLong ( hWnd , GWL_WNDPROC , SubClassWndProc ) 把窗口函數設置成子類化窗口函數,完成窗口子類化。
爲了減小子類化過程當中繁瑣的工做,MFC中提供了對子類化的支持,它簡化了子類化過程,利用CWnd類SubClassWindows()函數來實現子類化。爲了讓讀者對子類化過程有一個直觀的認識,下面將利用MFC實現對一個編輯(Edit)控件的子類化。
(1)、建立一個從MFC控件類CEdit派生的新控件類CSubEdit。
(2)、添加CSubEdit::PreTranslateMessage(MSG* pMsg)
BOOL CSubEdit::PreTranslateMessage(MSG* pMsg)
{
//限於篇幅處理內容略。
return TRUE;
}
CEdit::PreTranslateMessage(pMsg);
}
(3)、在包含此控件的對話框類頭文件中控件變量類型從CEdit改成CSubEdit。
(4)、在包含此控件的對話框類文件中對Edit控件進行子類化,代碼以下:
HWND HwND;
m_subEdit.SubclassWindow(hWnd); //m_subEdit爲控件變量名。
5、小結
本文討論了實現消息攔截的兩種方法,其中鉤子技術用途普遍,不只能夠實現對同進程內消息的攔截,並且還能夠實現對另外進程消息的攔截。而子類化技術主要用於實現對同一進程單元模塊中的窗口消息的攔截。程序員能夠根據實際應用需求選擇其一來實現消息的擋截