Windows消息攔截技術的應用


Windows消息攔截技術的應用

民航合肥空管中心 周毅

 

一、前 言

眾所周知,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  //好像要改為SECTIONS  ,否則編譯有錯誤

sharedata READ WRITE SHARED 

 

另外一種方法:

也可以在代碼中直接設置: 
  #pragma data_seg("sharedata")  
.......... 
  #pragma data_seg()  
  #pragma comment(linker,"/SECTION:sharedata,RWS") 

 

(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為控件變量名。

 

五、小結

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

 

http://blog.csdn.net/jiangxinyu/article/details/5276538


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM