windows中使用鉤子攔截訊息

來源:互聯網
上載者:User

一、前 言

眾所周知,Windows程式的運行是依靠發生的事件來驅動。換句話說,程式不斷等待一個訊息的發生,然後對這個訊息的類型進行判斷,再做適當的處理。處理完此次訊息後又回到等待狀態。從上面對Windows程式運行機制的分析不難發現,訊息在使用者與程式之間進行交流時起了一種中間“語言”的作用。在程式中接收和處理訊息的主角是視窗,它通過訊息泵接收訊息,再通過一個視窗過程對訊息進行相應的處理。

訊息攔截的實現是在視窗過程處理訊息之前攔截到訊息並做相關處理後再傳送給原視窗過程。通常情況下,程式員可以在視窗過程中處理接收到的訊息,這就要求視窗過程必須在開發程式時完成,但是在一些應用中常常需要擷取和處理另外應用程式或其它單元模組中的訊息,實現此類功能的技術也就本文要討論的主題――訊息攔截技術。

二、理解Windows訊息機制

在深入探討訊息攔截技術實現原理之前,讓我們先來溫習一下Windows訊息機制原理知識。

1、  訊息的產生

訊息作為程式與外界交流的“語言”,它的產生自然來自外界,但這裡所說的外界,不只是簡單的指程式之外或軟體系統之外,而是泛指訊息處理模組之外的模組、Windows系統、其它應用程式以及硬體等。通常根據訊息產生的方式將其分為兩大類,即硬體訊息和軟體訊息。硬體訊息,常指由硬體裝置所產生的事件(如滑鼠或鍵盤被按下),放在系統訊息佇列(System Queue)中,再由系統訊息處理機構將訊息發送給應用程式訊息佇列中。軟體訊息,常指由Windows系統或其它應用程式發送的資訊,它直接放入應用程式訊息佇列(Application
Queue)中,再由應用程式訊息處理機構將訊息傳遞給相應的視窗。

2、  訊息的組成

一個訊息由一個訊息名稱(UINT),和兩個參數(WPARAM,LPARAM)。當使用者進行了輸入或是視窗的狀態發生改變時系統都會發送訊息到某一個視窗。例如當菜單轉中之後會有WM_COMMAND訊息發送,WPARAM的高字中(HIWORD(wParam))是命令的ID號,對菜單來講就是菜單ID。當然使用者也可以定義自己的訊息名稱,也可以利用自訂訊息來發送通知和傳送資料。

3、  訊息的接收者

一個訊息必須由一個視窗接收。在視窗過程(WNDPROC)中可以對訊息進行分析,對應用程式要求處理的訊息進行相應的處理工作,對於那麼不需要應用程式處理的訊息可簡單的調用預設處理。例如你希望對菜單選擇進行處理那麼你可以定義對WM_COMMAND進行處理的代碼,如果希望在視窗中進行圖形輸出就必須對WM_PAINT進行處理。

4、  訊息的處理

視窗接收到發送給自己的訊息後,將訊息結構作為參數調用視窗過程對訊息進行相應的處理。可以將視窗過程看作訊息處理代碼的集合,視窗過程函數的原型為:

long FAR PASCAL WndProc(HWND hWnd,WORD message,WORD wParam,LONG lParam);
   其中,hWnd為視窗控制代碼,message為訊息名稱,wParam,lParam為兩個參數。

   在Windows中,應用程式不直接調用任何視窗函數,而是等待Windows調用視窗函數,請求完成任務或返回資訊。為保證Windows調用這個視窗函數,這個函數必須先向Windows登記,然後在Windows實施相應操作時回調,所以視窗函數又稱為回呼函數。WndProc是一個主回呼函數,Windows至少有一個回呼函數。它是在應用程式進行視窗類別註冊時向Windows登記的。

三、利用鉤子(Hook)攔截訊息

1、 何為鉤子(Hook)?

鉤子(Hook)機制允許應用程式截獲處理window訊息或特定事件。與DOS中斷截獲處理機制有類似之處。鉤子是Windows訊息處理機制的一個平台,應用程式可以在上面設定子程以監視指定視窗的某種訊息,而且所監視的視窗可以是其他進程所建立的。當訊息到達後,鉤子可以在目標視窗處理函數之前處理它並且可以阻止訊息的傳遞。每一個鉤子都有一個與之相關聯的指標列表,稱之為鉤子鏈表,該鏈表中的指標指向這個鉤子的各個處理子程。鉤子的種類很多,每種鉤子可以攔截並處理相應種類的訊息。當鉤子所監視的訊息出現時,Windows調用鏈表中的第一個鉤子子程,第一個過程完成後將訊息傳遞鏈表中的下一個鉤子子程,直至鏈表中所有鉤子子程都執行完成(注意:如果在其中有一個鉤子在執行完成前不執行訊息傳遞,其後面的鉤子過程和原視窗過程都不會再接收到訊息。)後將訊息返回給視窗過程。

2、 鉤子子程函數

鉤子子程是一個應用程式定義的回呼函數。用以監視系統或某一特定類型的事件,這些事件可以是與某一特定線程關聯的,也可以是系統中所有線程的事件。其函數原型為:

LRESULT CALLBACK HookProc  (  int nCode, WPARAM wParam, LPARAM lParam );

其中,nCode參數是Hook代碼,Hook子程使用這個參數來確定任務。這個參數的值依賴於Hook類型,每一種Hook都有自己的Hook代碼特徵字元集。 Windows系統提供了多種類型的鉤子,每一種類型的Hook可以使應用程式能夠監視不同類型的系統訊息處理機制。

wParam和lParam參數的值依賴於Hook代碼,但是它們的典型值是包含了關於發送或者接收訊息的資訊。

3、鉤子的安裝與卸載

鉤子的安裝是通過SDK API SetWindowsHookEx()來實現的,它將鉤子子程安裝到系統鉤子鏈表中。其函數原型
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn,HINSTANCE hMod, DWORD dwThreadId );
其中,idHook是指鉤子的類型。表一中列出部分鉤子的類型及其說明。

類型
說明

WH_CALLWNDPROC
系統在訊息發送到接收視窗過程之前調用此子程

WH_CALLWNDPROCRET Hooks
在視窗過程處理完訊息之後調用此子程

WH_GETMESSAGE
監視從GetMessage / PeekMessage函數返回的訊息

WH_KEYBOARD
監視輸入到訊息佇列中的鍵盤訊息

WH_MOUSE
監視輸入到訊息佇列中的滑鼠訊息

限於篇幅,其它訊息類型就不一一列出了。有關內容可參見MSDN。

lpfn是指鉤子子程的地址指標。如果dwThreadId參數為0 或是一個由別的進程建立的線程的標識,lpfn必 須指向DLL中的鉤子子程。除此以外,lpfn可以指向當前進程的一段鉤子子程代碼。

hMod是指應用程式執行個體的控制代碼。標識包含lpfn所指的子程的DLL。如果dwThreadId 標識當前進程建立的一個線程,而且子程代碼位於當前進程,hMod必須為NULL。

dwThreadId是指與安裝的鉤子子程相關聯的線程的標識符,如果為0,鉤子子程與所有的線程關聯。

函數成功則返回鉤子的控制代碼,失敗返回NULL。

鉤子在使用完之後需要用UnHookWindowsHookEx()卸載,否則會造成麻煩。卸載鉤子比較簡單,UnHookWindowsHookEx()只有一個參數。函數原型如下:

UnHookWindowsHookEx  ( HHOOK hhk  )

其中,參數hhk是SetWindowsHookEx()函數返回鉤子控制代碼,所以設計程式時一定要儲存好這個控制代碼,以便卸載時使用。函數成功返回TRUE,否則返回FALSE。

4、系統鉤子與線程鉤子

Windows系統根據鉤子監視事件的範圍將鉤子分為系統鉤子(全域鉤子)和線程鉤子(局部鉤子)兩種。由SetWindowsHookEx()函數的最後一個參數決定了此鉤子是系統鉤子還是線程鉤子。線程勾子用於監視指定線程的事件訊息。線程勾子一般在當前線程或者當前線程派生的線程內。 系統勾子監視系統中的所有線程的事件訊息。因為系統勾子會影響系統中所有的應用程式,所以勾子函數必須放在獨立的動態連結程式庫(DLL) 中。系統自動將包含"鉤子回呼函數"的DLL映射到受鉤子函數影響的所有進程的地址空間中,即將這個DLL注入了那些進程。

5、  鉤子的實現

本文的執行個體實現攔截記事本(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中加入#include “NotePadHook.h”。

(4)、在NotePadHook.cpp中加入共用資料區段:

#pragma data_seg("sharedata")  //共用資料區段,段內的變數可被鉤子所有執行個體共用。

HHOOK glhHook=NULL;  //鉤子控制代碼。

HINSTANCE glhInstance=NULL;  //DLL執行個體控制代碼。

#pragma data_seg() 

(5)、僅定義一個資料區段還不能達到共用資料的目的,還要告訴編譯器該段的屬性。要在.DEF檔案中設定段的屬性,開啟.DEF檔案加入如下代碼:

SETCTIONS

sharedata READ WRITE SHARED

(6)、在主檔案NotePadHook.cpp的DllMain函數中加入儲存DLL執行個體控制代碼:

DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)

{

      //如果使用lpReserved參數則刪除下面這行

      UNREFERENCED_PARAMETER(lpReserved);

      if (dwReason == DLL_PROCESS_ATTACH)

      {

            TRACE0("NOtePadHOOK.DLL Initializing!\n");

             //擴充DLL僅初始化一次

           if (!AfxInitExtensionModule(NotePadHookDLL, hInstance))

                 return 0;

            new CDynLinkLibrary(NotePadHookDLL);

//把DLL加入動態MFC類庫中

            glhInstance = hInstance;

       //插入儲存DLL執行個體控制代碼

      }

      else if (dwReason == DLL_PROCESS_DETACH)

      {

            TRACE0("NotePadHOOK.DLL Terminating!\n");

           //終止這個連結庫前調用它

            AfxTermExtensionModule(NotePadHookDLL);

      }

      return 1;  

}

(7)、類CNotePadHook的成員函數的具體實現:

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)、鉤子子程的實現:

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);//繼續傳遞訊息。

}

(9)、編譯項目產生NotePadHook.dll。

雖然已經完成了鉤子類,但還不能實現鉤子功能。我們必須寫一個程式來啟動鉤子,將鉤子DLL注入其它程式的記憶體空間並將鉤子加入到系統鉤子鏈表中。由於限於篇幅,在本文就不具體講述鉤子啟動程式的執行個體,只將編寫啟動程式應注意的事項說明如下:

(1)、將NotePadHook項目中Debug\NotePadHook.lib加入到項目設定連結標籤中。

(2)、將NotePadHook項目中NotePadHook.h檔案include到stdafx.h。

(3)、首先需要建立一個CNotePadHook類執行個體,啟動鉤子時調用類成員StartHook(),卸載鉤子時調用類成員StopHook()。

四、利用視窗子類化(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)

{

   if( pMsg->message==WM_KEYDOWN&&pMsg->wParam==VK_RETURN)

   {

  //當在Edit控制項上按下斷行符號鍵後…

…..

//限於篇幅處理內容略。

return TRUE;

}

CEdit::PreTranslateMessage(pMsg);

}

(3)、在包含此控制項的對話方塊類標頭檔中控制項變數類型從CEdit改為CSubEdit。

(4)、在包含此控制項的對話方塊類檔案中對Edit控制項進行子類化,代碼如下:

 HWND HwND;

  GetDlgItem(IDC_SUB_EDIT,&hWnd);//其中IDC_SUB_EDIT是控制項ID。

 m_subEdit.SubclassWindow(hWnd); //m_subEdit為控制項變數名。

五、小結

本文討論了實現訊息攔截的兩種方法,其中鉤子技術用途廣泛,不僅可以實現對同進程內訊息的攔截,而且還可以實現對另外進程訊息的攔截。而子類化技術主要用於實現對同一進程單元模組中的視窗訊息的攔截。程式員可以根據實際應用需求選擇其一來實現訊息的擋截。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.