程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> 淺談HOOK技術在VC編程中的應用

淺談HOOK技術在VC編程中的應用

編輯:vc教程

  摘要: 本文針對HOOK技術在VC編程中的應用進行討論,並著重對應用比較廣泛的全局HOOK做了闡述。

  引言

  Windows操作系統是建立在事件驅動機制之上的,系統各部分之間的溝通也都是通過消息的相互傳遞而實現的。但在通常情況下,應用程序只能處理來自進程內部的消息或是從其他進程發過來的消息,如果需要對在進程外傳遞的消息進行攔截處理就必須采取一種被稱為HOOK(鉤子)的技術。鉤子是Windows操作系統中非常重要的一種系統接口,用它可以輕松截獲並處理在其他應用程序之間傳遞的消息,並由此可以完成一些普通應用程序難以實現的特殊功能。基於鉤子在消息攔截處理中的強大功能,本文即以VC++ 6.0為編程背景對鉤子的基本概念及其實現過程展開討論。為方便理解,在文章最後還給出了一個簡單的有關鼠標鉤子的應用示例。

  鉤子的基本原理

  鉤子的本質是一段用以處理系統消息的程序,通過系統調用,將其掛入到系統。鉤子的種類有很多,每一種鉤子負責截獲並處理相應的消息。鉤子機制允許應用程序截獲並處理發往指定窗口的消息或特定事件,其監視的窗口即可以是本進程內的也可以是由其他進程所創建的。在特定的消息發出,並在到達目的窗口之前,鉤子程序先行截獲此消息並得到對其的控制權。此時在鉤子函數中就可以對截獲的消息進行各種修改處理,甚至強行終止該消息的繼續傳遞。

  任何一個鉤子都由系統來維護一個指針列表(鉤子鏈表),其指針指向鉤子的各個處理函數。最近安裝的鉤子放在鏈的開始,最早安裝的鉤子則放在最後,當鉤子監視的消息出現時,操作系統調用鏈表開始處的第一個鉤子處理函數進行處理,也就是說最後加入的鉤子優先獲得控制權。在這裡提到的鉤子處理函數必須是一個回調函數(callback function),而且不能定義為類成員函數,必須定義為普通的C函數。在使用鉤子時可以根據其監視范圍的不同將其分為全局鉤子和線程鉤子兩大類,其中線程鉤子只能監視某個線程,而全局鉤子則可對在當前系統下運行的所有線程進行監視。顯然,線程鉤子可以看作是全局鉤子的一個子集,全局鉤子雖然功能強大但同時實現起來也比較煩瑣:其鉤子函數的實現必須封裝在動態鏈接庫中才可以使用。

  鉤子的安裝與卸載

  由於全局鉤子具有相當的廣泛性而且在功能上完全覆蓋了線程鉤子,因此下面就主要對應用較多的全局鉤子的安裝與使用進行討論。前面已經提過,操作系統是通過調用鉤子鏈表開始處的第一個鉤子處理函數而進行消息攔截處理的。因此,為了設置鉤子,只需將回調函數放置於鏈首即可,操作系統會使其首先被調用。在具體實現時由函數SetWindowsHookEx()負責將回調函數放置於鉤子鏈表的開始位置。SetWindowsHookEx()函數原型聲明如下:

HHOOK SetWindowsHookEx(int idHook;
HOOKPROC lpfn;
HINSTANCE hMod;
DWord dwThreadId);

  其中:參數idHook 指定了鉤子的類型,總共有如下13種:

   WH_CALLWNDPROC 系統將消息發送到指定窗口之前的"鉤子"
   WH_CALLWNDPROCRET 消息已經在窗口中處理的"鉤子"
   WH_CBT 基於計算機培訓的"鉤子"
   WH_DEBUG 差錯"鉤子"
   WH_FOREGROUNDIDLE 前台空閒窗口"鉤子"
   WH_GETMESSAGE 接收消息投遞的"鉤子"
   WH_JOURNALPLAYBACK 回放以前通過WH_JOURNALRECORD"鉤子"記錄的輸入消息
   WH_JOURNALRECORD 輸入消息記錄"鉤子"
   WH_KEYBOARD 鍵盤消息"鉤子"
   WH_MOUSE 鼠標消息"鉤子"
   WH_MSGFILTER 對話框、消息框、菜單或滾動條輸入消息"鉤子"
   WH_SHELL 外殼"鉤子"
   WH_SYSMSGFILTER 系統消息"鉤子"

  參數lpfn為指向鉤子處理函數的指針,即回調函數的首地址;參數hMod則標識了鉤子處理函數所處模塊的句柄;第四個參數dwThreadId 指定被監視的線程,如果明確指定了某個線程的ID就只監視該線程,此時的鉤子即為線程鉤子;如果該參數被設置為0,則表示此鉤子為監視系統所有線程的全局鉤子。此函數在執行完後將返回一個鉤子句柄。

  雖然對於線程鉤子並不要求其象全局鉤子一樣必須放置於動態鏈接庫中,但是推薦其也在動態鏈接庫中實現。因為這樣的處理不僅可使鉤子可為系統內的多個進程訪問,也可以在系統中被直接調用,而且對於一個只供單進程訪問的鉤子,還可以將其鉤子處理過程放在安裝鉤子的同一個線程內,此時SetWindowsHookEx()函數的第三個參數也就是該線程的實例句柄。

  在SetWindowsHookEx()函數完成對鉤子的安裝後,如果被監視的事件發生,系統馬上會調用位於相應鉤子鏈表開始處的鉤子處理函數進行處理,每一個鉤子處理函數在進行相應的處理時都要考慮是否需要把事件傳遞給下一個鉤子處理函數。如果要傳遞,就通過函數CallNestHookEx()來解決。盡管如此,在實際使用時還是強烈推薦無論是否需要事件傳遞而都在過程的最後調用一次CallNextHookEx( )函數,否則將會引起一些無法預知的系統行為或是系統鎖定。該函數將返回位於鉤子鏈表中的下一個鉤子處理過程的地址,至於具體的返回值類型則要視所設置的鉤子類型而定。該函數的原型聲明如下:

LRESULT CallNextHookEx(HHOOK hhk;int nCode;WPARAM wParam;LPARAM lParam);

  其中,參數hhk為由SetWindowsHookEx()函數返回的當前鉤子句柄;參數nCode為傳給鉤子過程的事件代碼;參數wParam和lParam 則為傳給鉤子處理函數的參數值,其具體含義同設置的鉤子類型有關。

  最後,由於安裝鉤子對系統的性能有一定的影響,所以在鉤子使用完畢後應及時將其卸載以釋放其所占資源。釋放鉤子的函數為UnhookWindowsHookEx(),該函數比較簡單只有一個參數用於指定此前由SetWindowsHookEx()函數所返回的鉤子句柄,原型聲明如下:

BOOL UnhookWindowsHookEx(HHOOK hhk);

  鼠標鉤子的簡單示例

  最後,為更清楚展示HOOK技術在VC編程中的應用,給出一個有關鼠標鉤子使用的簡單示例。在鉤子設置時采用的是全局鉤子。下面就對鼠標鉤子的安裝、使用以及卸載等過程的實現進行講述:

  由於本例程需要使用全局鉤子,因此首先構造全局鉤子的載體--動態鏈接庫。考慮到 Win32 DLL與Win16 DLL存在的差別,在Win32環境下要在多個進程間共享數據,就必須采取一些措施將待共享的數據提取到一個獨立的數據段,並通過def文件將其屬性設置為讀寫共享:

#pragma data_seg("TestData")
HWND glhPrevTarWnd=NULL; // 窗口句柄
HWND glhHook=NULL; // 鼠標鉤子句柄
HINSTANCE glhInstance=NULL; // DLL實例句柄
#pragma data_seg()
……
SECTIONS // def文件中將數據段TestData設置為讀寫共享
TestData READ WRITE SHARED

  在安裝全局鼠標鉤子時使用函數SetWindowsHookEx(),並設定鼠標鉤子的處理函數為MouseProc(),安裝函數返回的鉤子句柄保存於變量glhHook中:

void StartHook(HWND hWnd)
{
……
glhHook=(HWND)SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0);
}

  鼠標鉤子安裝好後,在移動、點擊鼠標時將會發出鼠標消息,這些消息均經過消息處理函數MouseProc()的攔截處理。在此,每當捕獲到系統各線程發出的任何鼠標消息後首先獲取當前鼠標所在位置下的窗口句柄,並進一步通過GetWindowText()函數獲取到窗口標題。在處理函數完成後,通過CallNextHookEx()函數將事件傳遞到鉤子列表中的下一個鉤子處理函數:

LRESULT WINAPI MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lParam;
if(nCode>=0)
{
HWND glhTargetWnd=pMouseHook->hwnd;
//取目標窗口句柄
HWND ParentWnd=glhTargetWnd;
while(ParentWnd !=NULL)
{
glhTargetWnd=ParentWnd;
//取應用程序主窗口句柄
ParentWnd=GetParent(glhTargetWnd);
}
if(glhTargetWnd!=glhPrevTarWnd)
{
char szCaption[100];
//取目標窗口標題
GetWindowText(glhTargetWnd,szCaption,100);
……
}
}
//繼續傳遞消息
return CallNextHookEx((HHOOK)glhHook,nCode,wParam,lParam);
}

  最後,調用UnhookWindowsHookEx()函數完成對鉤子的卸載:

void StopHook()
{
……
UnhookWindowsHookEx((HHOOK)glhHook);
}

  現在完成的是鼠標鉤子的動態鏈接庫,經過編譯後需要經應用程序的調用才能實現對當前系統下各線程間鼠標消息的攔截處理。這部分同普通動態鏈接庫的使用沒有任何區別,在將其加載到進程後,首先調用動態鏈接庫的StartHook()函數安裝好鉤子,此時即可對系統下的鼠標消息實施攔截處理,在動態鏈接庫被卸載即終止鼠標鉤子時通過動態鏈接庫中的StopHook()函數卸載鼠標鉤子。

  經上述編程,在安裝好鼠標鉤子後,鼠標在移動到系統任意窗口上時,馬上就會通過對鼠標消息的攔截處理而獲取到當前窗口的標題。實驗證明此鼠標鉤子的安裝、使用和卸載過程是正確的。

  小結

  鉤子,尤其是系統鉤子具有相當強大的功能,通過這種技術可以對幾乎所有的Windows系統消息和事件進行攔截處理。這種技術廣泛應用於各種自動監控系統對進程外消息的監控處理。本文只對鉤子的一些基本原理和一般的使用方法做了簡要的探討,感興趣的讀者完全可以在本文所述代碼基礎之上用類似的方法實現對諸如鍵盤鉤子、外殼鉤子等其他類型鉤子的安裝與使用。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved