SetWindowsHookEx 鈎子


基本介紹

鈎子(Hook),是Windows消息處理機制的一個平台, 應用程序可以在上面設置子程以監視指定窗口的某種消息,而且所監視的窗口可以是其他進程所創建的。當消息到達后,在目標窗口處理函數之前處理它。鈎子機制允許 應用程序截獲處理window消息或特定事件。
鈎子實際上是一個處理消息的 程序段,通過 系統調用,把它掛入系統。每當特定的消息發出,在沒有到達目的窗口前, 鈎子程序就先捕獲該消息,亦即 鈎子函數先得到控制權。這時 鈎子函數即可以加工處理(改變)該消息,也可以不作處理而繼續傳遞該消息,還可以強制結束消息的傳遞。

聲明

HHOOK WINAPI SetWindowsHookEx(
__in int idHook,
__in HOOKPROC lpfn,
__in HINSTANCE hMod,
__in DWORD dwThreadId);
VB版: Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long

原型

SetWindowsHookEx(WH_KEYBOARD, KeyBoardProc, HInstance, 0);

返回值

返回值:若此函數執行成功,則返回值就是該掛鈎處理過程的句柄;若此函數執行失敗,則返回值為NULL(0).若想獲得更多錯誤信息,請調用GetLasError函數.

包含機制

鈎子鏈表

每一個Hook都有一個與之相關聯的 指針列表,稱之為鈎子 鏈表,由系統來維護。這個列表的 指針指向指定的,應用程 序定義的,被Hook子程調用的 回調函數,也就是該鈎子的各個處理子程。當與指定的Hook類型關聯的消息發生時,系統就把這個消息傳遞到Hook子程。 一些Hook子程可以只監視消息,或者修改消息,或者停止消息的前進,避免這些消息傳遞到下一個Hook子程或者目的窗口。最后安裝的鈎子放在鏈的開始, 而最早安裝的鈎子放在最后,也就是后加入的先獲得控制權。
Windows 並不要求鈎子子程的 卸載順序一定得和安裝順序相反。每當有一個鈎子被 卸載,Windows 便釋放其占用的內存,並更新整個Hook鏈表。如果程序安裝了鈎子,但是在尚未 卸載鈎子之前就結束了,那么系統會自動為它做卸載鈎子的操作。

鈎子子程

鈎子子程是一個 應用程序定義的 回調函數(CALLBACK Function),不能定義成某個 類的成員函數,只能定義為普通的C函數。用以監視系統或某一特定類型的事件,這些事件可以是與某一特定線程關聯的,也可以是系統中所有線程的事件。
函數語法:
LRESULT CALLBACK HookProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
);
HookProc是 回調函數名。
nCode參數是Hook代碼,Hook子程使用這個參數來確定任務。這個參數的值依賴於Hook類型,每一種Hook都有自己的Hook代碼特征 字符集
wParam和lParam參數的值依賴於Hook代碼,但是它們的典型值是包含了關於發送或者接收消息的信息。

安裝釋放

使用API函數SetWindowsHookEx()把一個 應用程序定義的鈎子子程安裝到鈎子 鏈表中。 SetWindowsHookEx函數總是在Hook鏈的開頭安裝Hook子程。當指定類型的Hook監視的事件發生時,系統就調用與這個Hook關聯的 Hook鏈的開頭的Hook子程。每一個Hook鏈中的Hook子程都決定是否把這個事件傳遞到下一個Hook子程。Hook子程傳遞事件到下一個 Hook子程需要調用CallNextHookEx函數。
HHOOK SetWindowsHookEx(
int idHook, // 鈎子的類型,即它處理的消息類型
HOOKPROC lpfn, // 鈎子子程的地址 指針。如果dwThreadId參數為0
// 或是一個由別的進程創建的線程的標識,
// lpfn必須指向DLL中的鈎子子程。
// 除此以外,lpfn可以指向當前進程的一段鈎子子程代碼。
//  鈎子函數的入口地址,當鈎子鈎到任何消息后便調用這個函數。
HINSTANCE hMod, //  應用程序實例的句柄。標識包含lpfn所指的子程的
DLL。
// 如果dwThreadId 標識當前進程創建的一個線程,
// 而且子程代碼位於當前進程,hMod必須為NULL。
// 可以很簡單的設定其為本 應用程序的實例句柄。
DWORD dwThreadId // 與安裝的鈎子子程相關聯的線程的 標識符
// 如果為0,鈎子子程與所有的線程關聯,即為 全局鈎子
);
以上所說的鈎子子程與線程相關聯是指在一鈎子鏈表中發給該線程的消息同時發送給鈎子子程,且被鈎子子程先處理。
在鈎子子程中調用得到控制權的 鈎子函數在完成對消息的處理后,如果想要該消息繼續傳遞,那么它必須調用另外一個 SDK中的API函數CallNextHookEx來傳遞它,以執行鈎子 鏈表所指的下一個鈎子子程。這個函數成功時返回鈎子鏈中下一個鈎子過程的返回值, 返回值的類型依賴於鈎子的類型。這個函數的原型如下:
LRESULT CallNextHookEx
(
HHOOK hhk;
int nCode;
WPARAM wParam;
LPARAM lParam;
);
hhk為當前鈎子的句柄,由SetWindowsHookEx()函數返回。
NCode為傳給鈎子過程的事件代碼。
wParam和lParam 分別是傳給鈎子子程的wParam值,其具體含義與鈎子類型有關。
鈎子函數也可以通過直接返回TRUE來丟棄該消息,並阻止該消息的傳遞。否則的話,其他安裝了鈎子的 應用程序將不會接收到鈎子的通知而且還有可能產生不正確的結果。
鈎子在使用完之后需要用UnhookWindowsHookEx() 卸載,否則會造成麻煩。釋放鈎子比較簡單, [1] ()只有一個參數。函數原型如下:
UnhookWindowsHookEx
(
HHOOK hhk;
);
函數成功返回TRUE,否則返回FALSE。

運行機制

:在Win16環境中,DLL的全局數據對每個載入它的進程來說都是相同的;而在Win32環境中,情況卻發生了變化,DLL函數中的代碼所創建的任何對象(包括 變量)都歸調用它的線程或進程所有。當進程在載入DLL時, 操作系統自動把DLL 地址映射到該進程的私有空間,也就是進程的 虛擬地址空間,而且也復制該DLL的全局數據的一份拷貝到該進程空間。也就是說每個進程所擁有的相同的DLL的全局數據,它們的名稱相同,但其值卻並不一定是相同的,而且是互不干涉的。
因此,在Win32環境下要想在多個進程中共享數據,就必須進行必要的設置。在訪問同一個Dll的各進程 之間共享存儲器是通過 存儲器映射文件技術實現的。也可以把這些需要共享的數據分離出來,放置在一個獨立的數據段里,並把該段的屬性設置為共享。必須給這些 變量賦初值,否則 編譯器會把沒有賦初始值的變量放在一個叫未被初始化的 數據段中。
#pragma data_seg 預處理指令用於設置共享 數據段。例如:
#pragma data_seg("SharedDataName")
HHOOK hHook=NULL;
#pragma data_seg()
在#pragma data_seg("SharedDataName")和#pragma data_seg()之間的所有 變量將被訪問該Dll的所有進程看到和共享。再加上一條指令#pragma comment(linker,"/section:.SharedDataName,rws"),那么這個數據節中的數據可以在所有DLL的實例之間共 享。所有對這些數據的操作都針對同一個實例的,而不是在每個進程的 地址空間中都有一份。
當進程隱式或顯式調用一個動態 庫里的函數時,系統都要把這個動態庫映射到這個進程的虛擬 地址空間里(以下簡稱"地址空間")。這使得DLL成為進程的一部分,以這個進程的身份執行,使用這個進程的 堆棧

系統鈎子

SetWindowsHookEx()函數的最后一個參數決定了此鈎子是 系統鈎子還是線程鈎子。

線程鈎子

線程勾子用於監視指定線程的事件消息。線程勾子一般在當前線程或者當前線程派生的線程內。
系統勾子監視系統中的所有線程的事件消息。因為系統勾子會影響系統中所有的 應用程序,所以勾子函數必須放在獨立的 動態鏈接庫(DLL) 中。系統自動將包含"鈎子回調函數"的DLL映射到受 鈎子函數影響的所有進程的 地址空間中,即將這個DLL注入了那些進程。

編輯本段說明

(1)如果對於同一事件(如鼠標消息)既安裝了線程鈎子又安裝了 系統鈎子,那么系統會自動先調用線程鈎子,然后調用系統鈎子。
(2)對同一事件消息可安裝多個鈎子處理過程,這些勾子處理過程形成了鈎子鏈。當前鈎子處理結束后應把鈎子信息傳遞給下一個 鈎子函數
(3)鈎子特別是 系統鈎子會消耗消息處理時間,降低系統性能。只有在必要的時候才安裝鈎子,在使用完畢后要及時 卸載

類型

每一種類型的Hook可以使 應用程序能夠監視不同類型的 系統消息處理機制。下面描述所有可以利用的Hook類型。
1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks
WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以監視發送到窗口過程的消息。系統在消息發送到接收窗口過程之前調用WH_CALLWNDPROC Hook子程,並且在窗口過程處理完消息之后調用WH_CALLWNDPROCRET Hook子程。
WH_CALLWNDPROCRET Hook傳遞 指針到CWPRETSTRUCT結構,再傳遞到Hook子程。
CWPRETSTRUCT結構包含了來自處理消息的窗口過程的返回值,同樣也包括了與這個消息關聯的消息參數。
2、WH_CBT Hook
在以下事件之前,系統都會調用WH_CBT Hook子程,這些事件包括:
1)激活,建立,銷毀,最小化,最大化,移動,改變尺寸等窗口事件;
2)完成系統指令;
3)來自系統消息隊列中的移動鼠標, 鍵盤事件;
4)設置 輸入焦點事件;
5)同步系統消息隊列事件。
Hook子程的返回值確定系統是否允許或者防止這些操作中的一個。
3、WH_DEBUG Hook
系統調用系統中與其他Hook關聯的Hook子程之前,系統會調用WH_DEBUG Hook子程。你可以使用這個Hook來決定是否允許 系統調用與其他Hook關聯的Hook子程。
4、WH_FOREGROUNDIDLE Hook
應用程序的前台 線程處於空閑狀態時,可以使用WH_FOREGROUNDIDLE Hook執行低優先級的任務。當 應用程序的前台 線程大概要變成空閑狀態時,系統就會調用WH_FOREGROUNDIDLE Hook子程。
5、WH_GETMESSAGE Hook
應用程序使用WH_GETMESSAGE Hook來監視從GetMessage or PeekMessage函數返回的消息。你可以使用WH_GETMESSAGE Hook去監視鼠標和 鍵盤輸入,以及其他發送到 消息隊列中的消息。
6、WH_JOURNALPLAYBACK Hook
WH_JOURNALPLAYBACK Hook使 應用程序可以插入消息到系統消息隊列。可以使用這個Hook回放通過使用WH_JOURNALRECORD Hook記錄下來的連續的鼠標和 鍵盤事件。只要WH_JOURNALPLAYBACK Hook已經安裝,正常的鼠標和 鍵盤事件就是無效的。
WH_JOURNALPLAYBACK Hook是全局Hook,它不能象線程特定Hook一樣使用。
WH_JOURNALPLAYBACK Hook返回超時值,這個值告訴系統在處理來自回放Hook當前消息之前需要等待多長時間(毫秒)。這就使Hook可以控制實時事件的回放。
WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。
7、WH_JOURNALRECORD Hook
WH_JOURNALRECORD Hook用來監視和記錄輸入事件。典型的,可以使用這個Hook記錄連續的鼠標和 鍵盤事件,然后通過使用WH_JOURNALPLAYBACK Hook來回放。
WH_JOURNALRECORD Hook是全局Hook,它不能象線程特定Hook一樣使用。
WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。
8、WH_KEYBOARD Hook
應用程序中,WH_KEYBOARD Hook用來監視WM_KEYDOWN and WM_KEYUP消息,這些消息通過GetMessage or PeekMessage function返回。可以使用這個Hook來監視輸入到 消息隊列中的 鍵盤消息。
9、WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook監視輸入到線程 消息隊列中的 鍵盤消息。
10、WH_MOUSE Hook
WH_MOUSE Hook監視從GetMessage 或者 PeekMessage 函數返回的鼠標消息。使用這個Hook監視輸入到 消息隊列中的鼠標消息。
11、WH_MOUSE_LL Hook
WH_MOUSE_LL Hook監視輸入到線程 消息隊列中的鼠標消息。
12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以監視 菜單,滾動條, 消息框,對話框消息並且發現用戶使用ALT+TAB or ALT+ESC 組合鍵切換窗口。WH_MSGFILTER Hook只能監視傳遞到 菜單,滾動條, 消息框的消息,以及傳遞到通過安裝了Hook子程的 應用程序建立的對話框的消息。WH_SYSMSGFILTER Hook監視所有 應用程序消息。
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以在模式循環期間過濾消息,這等價於在主 消息循環中過濾消息。
通過調用CallMsgFilter function可以直接的調用WH_MSGFILTER Hook。通過使用這個函數, 應用程序能夠在模式循環期間使用相同的代碼去過濾消息,如同在主 消息循環里一樣。
13、WH_SHELL Hook
外殼 應用程序可以使用WH_SHELL Hook去接收重要的通知。當外殼 應用程序是激活的並且當頂層窗口建立或者銷毀時, 系統調用WH_SHELL Hook子程。
WH_SHELL 共有5鍾情況:
1)只要有個top-level、unowned 窗口被產生、起作用、或是被摧毀;
2)當Taskbar需要重畫某個按鈕;
3)當系統需要顯示關於Taskbar的一個程序的最小化形式;
4)當如今的 鍵盤布局狀態改變;
5)當使用者按Ctrl+Esc去執行Task Manager(或相同級別的程序)。
按照慣例,外殼 應用程序都不接收WH_SHELL消息。所以,在 應用程序能夠接收WH_SHELL消息之前,應用程序必須調用SystemParametersInfo function注冊它自己。
也就是說,如果你的App沒有調用如下代碼注冊自己之前,那么它永遠也不會響應你的ShellProc:
//MINIMIZEDMETRICS mm = {sizeof(MINIMIZEDMETRICS), 0,0, ARW_HIDE};
MINIMIZEDMETRICS mm = {sizeof(MINIMIZEDMETRICS), 0,0, 0, ARW_HIDE};
SystemParametersInfo(SPI_SETMINIMIZEDMETRICS, sizeof(MINIMIZEDMETRICS) , &mm, 0);
 
 
例子
 
讓Mouse的右鍵無效,其實也就是攔截WM_RBUTTONDOWN/WM_RBUTTONUP,但是,如果只針
對某個Window/有hWnd的控制項,那便是使用SubClass來做,如果針對的是整個Process
那麽,用Mouse Hook比較好,然而這只針對單一個Process,而如何讓所有的Process都
如此,那得使用Remote的Mouse Hook,但這是要放在.Dll之中,而單純用VB來做我沒有
試出來。
'以下在Form
Private Sub Form_Load()
Call EnableHook
End Sub

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
Debug.Print X; Y
End Sub

Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
Debug.Print X; Y
End Sub

Private Sub Form_Unload(Cancel As Integer)
Call FreeHook
End Sub
'以下在.Bas
 Public Const WH_MOUSE = 7
 Public Const WM_RBUTTONDOWN = &H204
 Public Const HC_ACTION = 0
 Public Const WM_RBUTTONUP = &H205

 Declare Function SetWindowsHookEx Lib "user32" Alias _
    "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
    ByVal hmod As Long, ByVal dwThreadId As Long) As Long
 Declare Function UnhookWindowsHookEx Lib "user32" _
    (ByVal hHook As Long) As Long
 Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, _
    ByVal nCode As Long, ByVal wParam As Long, lParam As Any) As Long
 Declare Sub CopyMemory Lib "KERNEL32" Alias "RtlMoveMemory" _
   (lpvDest As Any, ByVal lpvSource As Long, ByVal cbCopy As Long)
 Declare Function ScreenToClient Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long
 Public theForm As Form
 Public hHook As Long   ' handle of Hook Procedure
 Sub EnableHook()
    If hHook = 0 Then
       hHook = SetWindowsHookEx(WH_MOUSE, AddressOf MouseHookProc, App.hInstance, 0)
    End If
 End Sub
 Sub FreeHook()
 Dim ret As Long
 If hHook <> 0 Then
    ret = UnhookWindowsHookEx(hHook)
    hHook = 0
 End If
 End Sub
 Function MouseHookProc(ByVal code As Long, ByVal wParam As Long, _
                 ByVal lParam As Long) As Long
  If code < 0 Then
     MouseHookProc = CallNextHookEx(hHook, code, wParam, lParam)
     Exit Function
  End If
  If wParam = WM_RBUTTONDOWN Or wParam = WM_RBUTTONUP Then
      MouseHookProc = 1 '表示不處理這個訊息
      Exit Function
  End If
  MouseHookProc = 0 '表示要處理這個訊息
  Call CallNextHookEx(hHook, code, wParam, lParam)
 End Function


免責聲明!

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



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