Windows鈎子 鈎子是Windows的消息處理機制中的一個監視點,應用程式可以在這裡安裝一個監視子程式,這樣就可以在系統中的消息流到達目的視窗過程前監控它們。 鈎子是Windows消息機制中的監視點,應用程式可以在這裡安裝一個監視函數,這樣就可以捕捉自己程序或者其他程序發生的事件。通過SetWindowsHookEx函數就可以做到這一點。SetWindowsHookEx函數定義了監視函數的位置和監視消息的類型,這樣,每當發生我們感興趣的消息時,Windows就會将消息發送給監視函數,監視函數是一個處理消息的回調函數,也稱為“鈎子函數”。 Windows安裝的鈎子有兩種類型:局部的和遠端的。它們處理消息的範圍不同。局部鈎子僅鈎挂屬于自身程序的事件;遠端鈎子除了可以鈎挂自身程序的事件,還可以鈎挂其他程序中發生的事件。遠端鈎子又分兩種:基于線程的和系統範圍的。基于線程的遠端鈎子用來捕獲其他程序中某一特定線程的事件;而系統範圍的遠端鈎子将捕捉系統中所有程序中發生的事件消息。 安裝鈎子會影響系統的性能,因為系統在處理所有的相關事件時都會調用鈎子函數,特别是監視範圍是整個系統範圍的全局鈎子。如果鈎子函數中的處理代碼過多的話,系統運作速度将會明顯減慢,是以對于全局鈎子一定要小心使用,不需要的時候應該立刻解除安裝。在 DOS作業系統下編寫中斷服務程式的時候,如果代碼有錯誤的話會影響其他調用它的程式。同樣,由于鈎子函數可以預先截獲其他程序的消息,是以一旦鈎子函數存在問題的話,也會影響其他程序的運作。 表1 鈎子的類型 鈎 子 名 稱 監視消息的類型和時機 WH_CALLWNDPROC 每當調用SendMessage函數時,函數将消息發送給目标視窗過程前首先調用鈎子函數 WH_CALLWNDPROCRET 每當調用SendMessage函數時,函數将消息發送給目标視窗過程後再調用鈎子函數 WH_GETMESSAGE 每當調用GetMessage或PeekMessage函數時,函數從程式的消息隊列中擷取一個消息後調 用鈎子函數 WH_KEYBOARD 每當調用GetMessage或PeekMessage函數時,如果從消息隊列中得到的是WM_KEYUP或 WM_KEYDOWN消息, 則調用鈎子函數 WH_MOUSE 每當調用GetMessage或PeekMessage函數時,如果從消息隊列中得到的是滑鼠消息, 則調用鈎子函數 WH_HARDWARE 每當調用GetMessage或PeekMessage函數時,如果從消息隊列中得到的是非滑鼠和鍵盤消息, 則調用鈎子函數 WH_MSGFILTER 當使用者對對話框、菜單和滾動條有所操作時,系統在發送對應的消息之前調用鈎子函數,這 種鈎子隻能是局部的 WH_SYSMSGFILTER 同WH_MSGFILTER,不過是系統範圍的 WH_SHELL 當Windows shell程式準備接收一些通知事件前調用鈎子函數,如shell被激活和重畫等 WH_DEBUG 用來給其他鈎子函數除錯 WH_CBT 當基于計算機的訓練(CBT)事件發生時調用鈎子函數 WH_JOURNALRECORD 日志記錄鈎子,用來記錄發送給系統消息隊列的所有消息 WH_JOURNALPLAYBACK 日志回放鈎子,用來回放日志記錄鈎子記錄的系統事件 WH_FOREGROUNDIDLE 系統空閑鈎子,當系統空閑的時候調用鈎子函數,這樣就可以在這裡安排一些優先級 很低的任務 在這些鈎子中,有些隻能當做局部鈎子使用,如WH_MSGFILTER鈎子;有些隻能當做系統範圍的遠端鈎子使用, 如WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK鈎子;而大多數的鈎子可以在任何範圍内使用。 對于不同的鈎子,由于它們處理的消息類型不同,是以鈎子函數的參數定義也是不同的,在具體的程式設計中,需要檢視Win32 API手冊來了解各種鈎子函數的參數定義。 另外,遠端鈎子和局部鈎子的程式結構也是不同的。當安裝了一個局部鈎子時,每當指定的事件發生,Windows就可以調用程序中的鈎子函數;但是若安裝的是遠端鈎子,系統不能從其他程序的位址空間中調用鈎子函數,因為兩個程序的位址空間是隔離的,由于系統中隻有DLL程式是可以插入到其他程序的位址空間中去的,是以遠端鈎子的鈎子函數必須位于一個動态連結庫中,而且必須是共享資料段的動态連結庫(因為寫遠端鈎子要用到動态連結庫,是以本書中将兩部分内容合在一章中介紹)。 但是也有兩個例外:日志記錄鈎子和日志回放鈎子雖然屬于遠端鈎子,但是它們的鈎子函數卻可以放在安裝鈎子的程式中,并不需要單獨放在一個動态連結庫中。Microsoft并沒有說明為什麼有這樣的例外,或許是這兩個鈎子是用來監控比較底層的硬體事件的,是以鈎子函數的調用并不是從其他程序的位址空間中發起的,而是從Windows内部發起的,是以不存在不同程序之間位址空間隔離的問題。 下面的以鍵盤鈎子為例來說明系統範圍遠端鈎子的安裝和使用,局部鈎子的使用步驟與之類似,隻不過不必将鈎子函數放在動态連結庫中而已,使用起來更加簡單,讀者可以舉一反三自己嘗試一下。 遠端鈎子的安裝和使用 1. 鈎子程式的結構 鈎子程式一般包括3個功能子產品: (1)主程式——用來實作界面或者其他功能。 (2)鈎子回調函數——用來接收系統發過來的消息。 (3)鈎子的安裝和解除安裝程式。 對于局部鈎子來說,這些子產品可以處在同一個可執行檔案中。而對于遠端鈎子來說,第2部分必須放在一個動态連結庫中,第3部分雖然沒有要求,但一般也放在動态連結庫中,這是因為鈎子建立以後得到一個鈎子句柄,這個句柄要在鈎子回調函數中以及解除安裝鈎子的時候用到,如果把這部分代碼放在主程式中的話,還需要建立一個函數将它傳回給動态連結庫,是以還不如直接放到庫中。 下面例子包括兩部分檔案: HookDll.asm和HookDll.def檔案用來生成動态連結庫; Main.asm和Main.rc是主程式部分。程式用一個系統範圍的遠端鈎子來實作監視所有鍵盤輸入的功能。由于安裝鈎子回調函數的動态連結庫要求是共享資料段的,是以請讀者注意Makefile中dll檔案的連結選項,它使用了/section:.bss,S選項。 NAME = Main DLL = Hookdll ML_FLAG = /c /coff LINK_FLAG = /subsystem:windows DLL_LINK_FLAG = /subsystem:windows /section:.bss,S $(DLL).dll $(NAME).exe: $(DLL).dll: $(DLL).obj $(DLL).def Link $(DLL_LINK_FLAG) /Def:$(DLL).def /Dll $(DLL).obj $(NAME).exe: $(NAME).obj $(NAME).res Link $(LINK_FLAG) $(NAME).obj $(NAME).res .asm.obj: ml $(ML_FLAG) $< .rc.res: rc $< clean: del *.obj del *.res del *.exp del *.lib HookDll.asm檔案的内容如下: .386 .model flat, stdcall option casemap :none include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib .data hInstance dd ? .data? hWnd dd ? hHook dd ? dwMessage dd ? szAscii db 4 dup (?) .code DllEntry proc _hInstance,_dwReason,_dwReserved Push _hInstance pop hInstance mov eax,TRUE ret DllEntry Endp HookProc proc _dwCode,_wParam,_lParam local @szKeyState[256]:byte invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam invoke GetKeyboardState,addr @szKeyState invoke GetKeyState,VK_SHIFT mov @szKeyState + VK_SHIFT,al mov ecx,_lParam shr ecx,16 invoke ToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0 mov byte ptr szAscii [eax],0 invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL xor eax,eax ret HookProc endp InstallHook proc _hWnd,_dwMessage push _hWnd pop hWnd push _dwMessage pop dwMessage invoke SetWindowsHookEx,WH_KEYBOARD,addr HookProc,/ hInstance,NULL mov hHook,eax ret InstallHook endp UninstallHook proc invoke UnhookWindowsHookEx,hHook ret UninstallHook endp End DllEntry 需要共享的變量被放在 .data?段中,如鈎子句柄和鈎住的按鍵内容等,僅dll程式的執行個體句柄不需要共享,不需要共享的變量放在 .data段中。動态連結庫的入口函數例行公事地傳回了一個TRUE來表示允許被裝入。程式中隻寫了3個函數,HookProc是鈎子回調函數,InstallHook和 UninstallHook函數是供主程式使用的鈎子安裝函數和解除安裝函數。這3個函數是需要導出的,是以HookDll.def檔案中包括了它們的名稱: EXPORTS HookProc InstallHook UninstallHook InstallHook子程式用來安裝鈎子,程式為它設計了兩個參數:視窗句柄和自定義消息ID。動态連結庫儲存這兩個參數,以便在鈎子回調函數收到消息的時候将截獲的按鍵通過自定義消息ID轉發給父視窗,這樣父視窗在初始化完成後隻需要等待自定義消息ID就可以了。 在子程式中,通過SetWindowsHookEx函數安裝鈎子。SetWindowsHookEx函數的用法是: invoke SetWindowsHookEx,idHook,lpHookProc,hInstance,dwThreadID .if eax mov hHook,eax .endif idHook參數指定鈎子的類型,它就是表11.1中列出的鈎子名稱。例子中要安裝的是鍵盤鈎子,是以使用WH_KEYBOARD。lpHookProc參數指出鈎子回調函數的位址。 hInstance 指定鈎子回調函數所在DLL的執行個體句柄。如果安裝的是局部鈎子的話,由于局部鈎子的回調函數并不需要放在動态連結庫中,這時這個參數就使用NULL。 dwThreadID是安裝鈎子後想監控的線程的ID号。該參數可以決定鈎子是局部的還是系統範圍的。如果參數指定的是自己程序中的某個線程ID号,那麼該鈎子是一個局部鈎子;如果指定的線程ID是另一個程序中某個線程的ID,那麼安裝的鈎子是一個局部的遠端鈎子;如果想要安裝系統範圍的全局鈎子的話,可以将這個參數指定為NULL,這樣鈎子就會被解釋成系統範圍的,可以用來監控所有的程序及它們的線程。 如果鈎子安裝成功,函數傳回鈎子句柄,否則傳回NULL。鈎子句柄必須被儲存下來,因為在回調函數和解除安裝鈎子的時候還要用到這個句柄。動态連結庫導出的另一個函數是UninstallHook,用來供主程式解除安裝鈎子。程式在這裡使用UnhookWidowHookEx函數解除安裝鈎子,這個函數的輸入參數隻有一個,就是安裝鈎子時傳回的句柄 現在來看主程式。Main.rc檔案的内容如下: #include #define ICO_MAIN 1000 #define DLG_MAIN 1000 #define IDC_TEXT 1001 ICO_MAIN ICON "Main.ico" DLG_MAIN DIALOG 208, 130, 234, 167 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "鍵盤鈎子" FONT 9, "宋體" { EDITTEXT IDC_TEXT, 5, 5, 224, 158, ES_MULTILINE | ES_AUTOVSCROLL | WS_BORDER | WS_VSCROLL | WS_TABSTOP | ES_READONLY } 資源腳本檔案中的定義很簡單,僅定義了一個對話框,對話框中有個多行的編輯控件,用來顯示“鈎住”的按鍵。Main.asm的内容如下: .386 .model flat, stdcall option casemap :none include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include Hookdll.inc includelib Hookdll.lib ICO_MAIN equ 1000 DLG_MAIN equ 1000 IDC_TEXT equ 1001 WM_HOOK equ WM_USER + 100h .code _ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam local @dwTemp mov eax,wMsg .if eax == WM_CLOSE invoke UninstallHook invoke EndDialog,hWnd,NULL .elseif eax == WM_INITDIALOG invoke InstallHook,hWnd,WM_HOOK .if ! eax invoke EndDialog,hWnd,NULL .endif .elseif eax == WM_HOOK mov eax,wParam .if al == 0dh mov eax,0a0dh .endif mov @dwTemp,eax invoke SendDlgItemMessage,hWnd,IDC_TEXT,/ EM_REPLACESEL,0,addr @dwTemp .else mov eax,FALSE ret .endif mov eax,TRUE ret _ProcDlgMain endp start: invoke GetModuleHandle,NULL invoke DialogBoxParam,eax,DLG_MAIN,NULL,/ offset _ProcDlgMain,NULL invoke ExitProcess,NULL end start 為了使用動态連結庫中的導出函數InstallHook和UninstallHook,在程式的開頭需要用include語句和includelib語句将動态連結庫的函數聲明和導入庫包含進來。 在對話框初始化的消息WM_INITDIALOG 中,程式調用InstallHook函數安裝鈎子,輸入的參數是主視窗句柄和自定義的消息ID:WM_HOOK(Windows系統中ID值在WM_USER以後的值都可以由使用者使用,在這裡将WM_HOOK定義為WM_USER+100h),這樣每當鈎子回調函數得到按鍵消息的時候,就可以通過這個消息ID通知主視窗。接下來程式對傳回值進行檢查,如果傳回值表示失敗則直接退出程式。在關閉對話框的WM_CLOSE消息中,程式調用UninstallHook函數解除安裝鈎子。 在平時,主程式等待自定義消息WM_HOOK,并将傳遞過來的按鍵字元串通過發送EM_REPLACESEL消息添加到編輯框中,在添加之前先檢測按鍵是否為Enter鍵,如果是,再人為插入一個換行符(0ah),以便将編輯框中的内容換行顯示。 2. 鈎子回調函數 現在回過頭來看HookDll.asm程式中的鈎子回調函數,回調函數的寫法一般如下: HookProc proc dwCode,wParam,lParam invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam ;處理消息的代碼 mov eax,傳回值 ret HookProc endp 各種類型鈎子的回調函數的參數都是這樣3個,但是它們的定義各不相同,就像視窗過程在收到各種不同消息的時候,wParam和lParam的定義也各不相同。不同類型的鈎子回調函數的傳回值定義也是各不相同的。 對于鍵盤鈎子來說,參數的定義如下所示。 ● dwCode——鍵盤消息的處理方式。如果是HC_ACTION,表示收到一個正常的擊鍵消息;如果是HC_NOREMOVE,表示對應消息并沒有從消息隊列中移去(當某個程序用指定PM_NOREMOVE 标志的PeekMessage函數擷取消息時就是如此)。 ● wParam——按鍵的虛拟碼(即Windows.inc中定義的VK_xxx值)。 ● lParam——按鍵的重複次數、掃描碼和标志等資料,不同資料位的定義如下: ■ 位0~15:按鍵的重複次數。 ■ 位16~23:按鍵的掃描碼。 ■ 位24:按鍵是否是擴充鍵(F1與F2等Fx鍵,小鍵盤數字鍵等),如果此位是1表示按鍵是擴充鍵。 ■ 位25~28:未定義。 ■ 位29:如果Alt鍵在按下狀态,此位置1,否則置0。 ■ 位30:按鍵的原先狀态,消息發送前按鍵原來是按下的,此位被設定為1,否則置0。 ■ 位31:按鍵的目前動作,如果是按鍵按下,那麼此位被設定為0;按鍵釋放的話被設定為1。 對于每個擊鍵動作,鈎子回調函數會在鍵按下和釋放的時候被調用兩次,隻需根據 lParam的位31中的标志來記錄一次,否則得到的是重複資訊。 另外,回調函數收到的參數是以按鍵的掃描碼和虛拟碼表示的,在送給主視窗前需要将它轉換成我們認識的ASCII碼,但虛拟碼或掃描碼和ASCII碼之間的對應關系并沒有規律,必須進行查表操作才能轉換。如果在程式中自己轉換的話,需要一個鍵碼對應表和查表程式。 Windows中現成的函數ToAscii可以完成這個功能并自動辨認按鍵的按下或釋放動作。代碼如下。 HookProc proc _dwCode,_wParam,_lParam local @szKeyState[256]:byte invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam invoke GetKeyboardState,addr @szKeyState invoke GetKeyState,VK_SHIFT mov @szKeyState + VK_SHIFT,al mov ecx,_lParam shr ecx,16 invoke ToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0 mov byte ptr szAscii [eax],0 invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL ... ToAscii函數的用法是: invoke ToAscii,dwVirtKey,uScanCode,lpKeyState,lpBuffer,uFlags dwVirtKey參數指定按鍵的虛拟碼,在使用時直接用鈎子回調函數的wParam參數就可以了,uScanCode指定按鍵的掃描碼,并用位15來表示是按鍵按下還是按鍵釋放,和回調函數的lParam參數對比可以看出,lParam參數的高16位就是需要的東西,是以程式将lParam右移16位後用做uScanCode參數。 lpKeyState指向一個256位元組的緩沖區,其中存放鍵盤中所有按鍵的目前狀态,一個位元組表示一個按鍵,數值為1表示按下,為0表示釋放,資料在緩沖區中的排列位置按照VK_xx虛拟碼的順序排列。這是為了讓函數得知鍵盤上各種控制鍵的狀态(如Shift,Alt和Ctrl等),因為這些鍵是否按下對轉換結果是有影響的,比如同樣是按鍵“1”,如果Shift鍵不按下,對應的就是“1”,按下的話函數必須傳回“!”才是正确的結果。當然不可能自己去填寫這個緩存區,使用GetKeyboardState函數就可以讓系統根據目前的鍵盤狀态填寫這個緩沖區。 lpBuffer指向一個緩存區,用來接收轉換後的ASCII碼,最後的uFlags參數表示目前是否有一個菜單在激活狀态,0表示沒有,1表示有菜單正在激活。 函數的傳回值表示轉換後傳回在lpBuffer緩沖區中的字元數量,它可能是0(如按鍵放開時不産生字元)、1或者是2,下面的語句根據傳回字元數将緩沖區中的字元尾部加上一個NULL: mov byte ptr szAscii [eax],0 對于Shift等控制鍵來說,GetKeyboardState函數傳回的狀态是區分左、右鍵的(分别對應VK_LSHIFT和VK_RSHIFT),而ToAscii函數檢測的是VK_SHIFT,不對Shift鍵進行處理的話,轉換結果可能是錯誤的,是以程式使用GetKeyState函數單獨擷取VK_SHIFT的狀态并手工修改緩沖區中VK_SHIFT位置的狀态。 轉換完成後,用PostMessage函數将轉換後的按鍵内容傳遞給主視窗,就大功告成了!不過要注意的還有兩點:首先是在這裡不要使用SendMessage函數,因為可能造成死循環;其次就是不要向主視窗傳遞位址,因為鈎子DLL被插入到其他程序的位址空間中運作,是以将位址傳回去可能是無效的。 不同類型鈎子回調函數傳回值的定義是不同的。對于鍵盤鈎子,傳回0表示允許Windows将消息轉發給目标視窗過程,傳回非0值表示讓Windows将消息丢棄,這樣鈎子函數可以檢測到按鍵動作,目标程式卻無法收到鍵盤消息,相當于所有的按鍵都失效了。 3. 鈎子鍊 Windows系統中可以同時存在多個同類型的鈎子,多個程式同時安裝同一種鈎子的時候就會出現這種情況,這些鈎子組成一個鈎子鍊,最近加入的鈎子放在連結清單的頭部,Windows負責為每種鈎子維護一個鈎子鍊。當一個事件發生的時候,Windows調用最後安裝的鈎子,然後由目前鈎子的回調函數發起調用下一個鈎子的動作,Windows收到這個動作後,再從連結清單中取出下一個鈎子的位址并将調用傳遞下去。 在大多數的情況下,一個鈎子回調函數最好把消息事件傳遞下去以便其他的鈎子都有獲得處理這一消息的機會。調用下一個鈎子函數是CallNextHookEx,該函數的用法是: invoke CallNextHookEx,hHook,dwCode,wParam,lParam hHook參數是目前鈎子的句柄,dwCode,wParam和lParam參數就是目前鈎子收到的參數,這個函數讓Windows調用鈎子鍊中的下一個鈎子。如果調用成功,函數的傳回值是下一個鈎子回調函數傳回的數值。 四: 日志記錄鈎子 日志記錄鈎子是一種特殊的鈎子,說它特殊是因為它是遠端鈎子,卻不用放在動态連結庫中,這就為監視系統範圍的消息提供了友善。本節中嘗試用日志記錄鈎子的辦法來實作鍵盤監視的功能,包括彙編源檔案RecHook.asm和資源腳本檔案RecHook.rc,其中RecHook.rc檔案的内容和上一個例子的Main.rc檔案是一樣的。 RecHook.asm檔案的内容如下: .386 .model flat, stdcall option casemap :none include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib ICO_MAIN equ 1000 DLG_MAIN equ 1000 IDC_TEXT equ 1001 .data? hInstance dd ? hWinMain dd ? hHook dd ? szAscii db 32 dup (?) .code HookProc proc _dwCode,_wParam,_lParam local @szKeyState[256]:byte invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam pushad .if _dwCode == HC_ACTION mov ebx,_lParam assume ebx:ptr EVENTMSG .if [ebx].message == WM_KEYDOWN invoke GetKeyboardState,addr @szKeyState invoke GetKeyState,VK_SHIFT mov @szKeyState + VK_SHIFT,al mov ecx,[ebx].paramH shr ecx,16 invoke ToAscii,[ebx].paramL,ecx,/ addr @szKeyState,addr szAscii,0 mov byte ptr szAscii [eax],0 .if szAscii == 0dh mov word ptr szAscii+1,0ah .endif invoke SendDlgItemMessage,hWinMain,IDC_TEXT,/ EM_REPLACESEL,0,addr szAscii .endif assume ebx:nothing .endif popad ret HookProc endp _ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam mov eax,wMsg .if eax == WM_CLOSE invoke UnhookWindowsHookEx,hHook invoke EndDialog,hWnd,NULL .elseif eax == WM_INITDIALOG push hWnd pop hWinMain invoke SetWindowsHookEx,WH_JOURNALRECORD,/ addr HookProc,hInstance,NULL .if eax mov hHook,eax .else invoke EndDialog,hWnd,NULL .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret _ProcDlgMain endp start: invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,eax,DLG_MAIN,NULL,/ offset _ProcDlgMain,NULL invoke ExitProcess,NULL end start 由于不再需要動态連結庫了,鈎子回調函數HookProc被移到了主程式中,也取消了InstallHook和UninstallHook兩個子程式,相應的内容直接放在WM_INITDIALOG和WM_CLOSE消息中完成。在WM_INITDIALOG消息中用下面的語句完成對鈎子的安裝: invoke SetWindowsHookEx,WH_JOURNALRECORD,addr HookProc,hInstance,NULL 參數WH_JOURNALRECORD表示安裝的鈎子是日志記錄鈎子。 由于鈎子回調函數也寫在主程式中,是以沒有必要再通過自定義的WM_HOOK消息來通信,在回調函數中使用ToAscii函數将監測到的按鍵掃描碼轉換成ASCII碼字元串以後,程式直接發送EM_REPLACESEL消息将它添加到編輯框中。 程式比較重要的一個不同點在于日志鈎子回調函數的參數定義不同,在這裡dwCode的參數定義如下: ● HC_ACTION——系統準備從消息隊列中移去一條消息,消息的具體資訊由lParam參數中指定的EVENTMSG結構定義。 ● HC_SYSMODALOFF——某個系統模态對話框準備被關閉。 ● HC_SYSMODALON——某個系統模态對話框準備被建立。 我們關心的是HC_ACTION标志,這時lParam參數指向一個EVENTMSG結構,其定義為: EVENTMSG STRUCT message DWORD ? ;消息隊列中将要移去的消息ID paramL DWORD ? ;消息的wParam參數 paramH DWORD ? ;消息的lParam參數 time DWORD ? ;消息發生的事件 hwnd DWORD ? ;消息對應的視窗句柄 EVENTMSG ENDS 由于日志記錄鈎子可以截獲的不僅是鍵盤消息,也有滑鼠等其他消息,是以需要有個地方指定消息類型,通過檢測EVENTMSG結構中的消息ID字段就可以得知截獲的究竟是什麼消息。如果關心的是按鍵消息的話,那麼發現消息ID為WM_KEYDOWN時進行處理就可以了,同理,如果關心的是滑鼠消息的話,使用日志記錄鈎子也可以完成滑鼠鈎子完成的工作。例子程式中的相關代碼如下: .if _dwCode == HC_ACTION mov ebx,_lParam assume ebx:ptr EVENTMSG .if [ebx].message == WM_KEYDOWN ;處理按鍵消息 .endif assume ebx:nothing .endif 日志記錄鈎子回調函數的傳回值沒有被定義。是以不管傳回什麼值對消息的傳遞都沒有影響。