PC輔導(dǎo):WINDOWS鉤子函數(shù)

字號(hào):

本課中我們將要學(xué)習(xí)WINDOWS鉤子函數(shù)的使用方法。WINDOWS鉤子函數(shù)的功能非常強(qiáng)大,有了它您可以探測(cè)其它進(jìn)程并且改變其它進(jìn)程的行為。
    理論:
    WINDOWS的鉤子函數(shù)可以認(rèn)為是WINDOWS的主要特性之一。利用它們,您可以捕捉您自己進(jìn)程或其它進(jìn)程發(fā)生的事件。通過(guò)“鉤掛”,您可以給WINDOWS一個(gè)處理或過(guò)濾事件的回調(diào)函數(shù),該函數(shù)也叫做“鉤子函數(shù)”,當(dāng)每次發(fā)生您感興趣的事件時(shí),WINDOWS都將調(diào)用該函數(shù)。一共有兩種類型的鉤子:局部的和遠(yuǎn)程的。
    局部鉤子僅鉤掛您自己進(jìn)程的事件。
    遠(yuǎn)程的鉤子還可以將鉤掛其它進(jìn)程發(fā)生的事件。遠(yuǎn)程的鉤子又有兩種:
    基于線程的 它將捕獲其它進(jìn)程中某一特定線程的事件。簡(jiǎn)言之,就是可以用來(lái)觀察其它進(jìn)程中的某一特定線程將發(fā)生的事件。
    系統(tǒng)范圍的 將捕捉系統(tǒng)中所有進(jìn)程將發(fā)生的事件消息。
    安裝鉤子函數(shù)將會(huì)影響系統(tǒng)的性能。監(jiān)測(cè)“系統(tǒng)范圍事件”的系統(tǒng)鉤子特別明顯。因?yàn)橄到y(tǒng)在處理所有的相關(guān)事件時(shí)都將調(diào)用您的鉤子函數(shù),這樣您的系統(tǒng)將會(huì)明顯的減慢。所以應(yīng)謹(jǐn)慎使用,用完后立即卸載。還有,由于您可以預(yù)先截獲其它進(jìn)程的消息,所以一旦您的鉤子函數(shù)出了問(wèn)題的話必將影響其它的進(jìn)程。記住:功能強(qiáng)大也意味著使用時(shí)要負(fù)責(zé)任。
    在正確使用鉤子函數(shù)前,我們先講解鉤子函數(shù)的工作原理。當(dāng)您創(chuàng)建一個(gè)鉤子時(shí),WINDOWS會(huì)先在內(nèi)存中創(chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu),該數(shù)據(jù)結(jié)構(gòu)包含了鉤子的相關(guān)信息,然后把該結(jié)構(gòu)體加到已經(jīng)存在的鉤子鏈表中去。新的鉤子將加到老的前面。當(dāng)一個(gè)事件發(fā)生時(shí),如果您安裝的是一個(gè)局部鉤子,您進(jìn)程中的鉤子函數(shù)將被調(diào)用。如果是一個(gè)遠(yuǎn)程鉤子,系統(tǒng)就必須把鉤子函數(shù)插入到其它進(jìn)程的地址空間,要做到這一點(diǎn)要求鉤子函數(shù)必須在一個(gè)動(dòng)態(tài)鏈接庫(kù)中,所以如果您想要使用遠(yuǎn)程鉤子,就必須把該鉤子函數(shù)放到動(dòng)態(tài)鏈接庫(kù)中去。當(dāng)然有兩個(gè)例外:工作日志鉤子和工作日志回放鉤子。這兩個(gè)鉤子的鉤子函數(shù)必須在安裝鉤子的線程中。原因是:這兩個(gè)鉤子是用來(lái)監(jiān)控比較底層的硬件事件的,既然是記錄和回放,所有的事件就當(dāng)然都是有先后次序的。所以如果把回調(diào)函數(shù)放在DLL中,輸入的事件被放在幾個(gè)線程中記錄,所以我們無(wú)法保證得到正確的次序。故解決的辦法是:把鉤子函數(shù)放到單個(gè)的線程中,譬如安裝鉤子的線程。
    鉤子一共有14種,以下是它們被調(diào)用的時(shí)機(jī):
    WH_CALLWNDPROC 當(dāng)調(diào)用SendMessage時(shí)
    WH_CALLWNDPROCRET 當(dāng)SendMessage的調(diào)用返回時(shí)
    WH_GETMESSAGE 當(dāng)調(diào)用GetMessage 或 PeekMessage時(shí)
    WH_KEYBOARD 當(dāng)調(diào)用GetMessage 或 PeekMessage 來(lái)從消息隊(duì)列中查詢WM_KEYUP 或 WM_KEYDOWN 消息時(shí)
    WH_MOUSE 當(dāng)調(diào)用GetMessage 或 PeekMessage 來(lái)從消息隊(duì)列中查詢鼠標(biāo)事件消息時(shí)
    WH_HARDWARE 當(dāng)調(diào)用GetMessage 或 PeekMessage 來(lái)從消息隊(duì)列種查詢非鼠標(biāo)、鍵盤消息時(shí)
    WH_MSGFILTER 當(dāng)對(duì)話框、菜單或滾動(dòng)條要處理一個(gè)消息時(shí)。該鉤子是局部的。它時(shí)為那些有自己的消息處理過(guò)程的控件對(duì)象設(shè)計(jì)的。
    WH_SYSMSGFILTER 和WH_MSGFILTER一樣,只不過(guò)是系統(tǒng)范圍的
    WH_JOURNALRECORD 當(dāng)WINDOWS從硬件隊(duì)列中獲得消息時(shí)
    WH_JOURNALPLAYBACK 當(dāng)一個(gè)事件從系統(tǒng)的硬件輸入隊(duì)列中被請(qǐng)求時(shí)
    WH_SHELL 當(dāng)關(guān)于WINDOWS外殼事件發(fā)生時(shí),譬如任務(wù)條需要重畫它的按鈕.
    WH_CBT 當(dāng)基于計(jì)算機(jī)的訓(xùn)練(CBT)事件發(fā)生時(shí)
    WH_FOREGROUNDIDLE 由WINDOWS自己使用,一般的應(yīng)用程序很少使用
    WH_DEBUG 用來(lái)給鉤子函數(shù)除錯(cuò)
    現(xiàn)在我們知道了一些基本的理論,現(xiàn)在開始講解如何安裝/卸載一個(gè)鉤子。
    要安裝一個(gè)鉤子,您可以調(diào)用SetWindowHookEx函數(shù)。該函數(shù)的原型如下:
    SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD
    HookType 是我們上面列出的值之一,譬如: WH_MOUSE, WH_KEYBOARD
    pHookProc 是鉤子函數(shù)的地址。如果使用的是遠(yuǎn)程的鉤子,就必須放在一個(gè)DLL中,否則放在本身代碼中
    hInstance 鉤子函數(shù)所在DLL的實(shí)例句柄。如果是一個(gè)局部的鉤子,該值為NULL
    ThreadID 是您安裝該鉤子函數(shù)后想監(jiān)控的線程的ID號(hào)。該參數(shù)可以決定該鉤子是局部的還是系統(tǒng)范圍的。如果該值為NULL,那么該鉤子將被解釋成系統(tǒng)范圍內(nèi)的,那它就可以監(jiān)控所有的進(jìn)程及它們的線程。如果您指定了您自己進(jìn)程中的某個(gè)線程ID 號(hào),那該鉤子是一個(gè)局部的鉤子。如果該線程ID是另一個(gè)進(jìn)程中某個(gè)線程的ID,那該鉤子是一個(gè)全局的遠(yuǎn)程鉤子。這里有兩個(gè)特殊情況:WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK總是代表局部的系統(tǒng)范圍的鉤子,之所以說(shuō)是局部,是因?yàn)樗鼈儧](méi)有必要放到一個(gè)DLL中。WH_SYSMSGFILTER 總是一個(gè)系統(tǒng)范圍內(nèi)的遠(yuǎn)程鉤子。其實(shí)它和WH_MSGFILTER鉤子類似,如果把參數(shù)ThreadID設(shè)成0的話,它們就完全一樣了。
    如果該函數(shù)調(diào)用成功的話,將在eax中返回鉤子的句柄,否則返回NULL。您必須保存該句柄,因?yàn)楹竺嫖覀冞€要它來(lái)卸載鉤子。
    要卸載一個(gè)鉤子時(shí)調(diào)用UnhookWidowHookEx函數(shù),該函數(shù)僅有一個(gè)參數(shù),就是欲卸載的鉤子的句柄。如果調(diào)用成功的話,在eax中返回非0值,否則返回NULL。
    現(xiàn)在您知道了如何安裝和卸載一個(gè)鉤子了,接下來(lái)我們將看看鉤子函數(shù)。.
    只要您安裝的鉤子的消息事件類型發(fā)生,WINDOWS就將調(diào)用鉤子函數(shù)。譬如您安裝的鉤子是WH_MOUSE類型,那么只要有一個(gè)鼠標(biāo)事件發(fā)生時(shí),該鉤子函數(shù)就會(huì)被調(diào)用。不管您安裝的時(shí)那一類型鉤子,鉤子函數(shù)的原型都時(shí)是一樣的:
    HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD
    nCode 指定是否需要處理該消息
    wParam 和 lParam 包含該消息的附加消息
    HookProc 可以看作是一個(gè)函數(shù)名的占位符。只要函數(shù)的原型一致,您可以給該函數(shù)取任何名字。至于以上的幾個(gè)參數(shù)及返回值的具體含義各種類型的鉤子都不相同。譬如:
    WH_CALLWNDPROC
    nCode 只能是HC_ACTION,它代表有一個(gè)消息發(fā)送給了一個(gè)窗口
    wParam 如果非0,代表正被發(fā)送的消息
    lParam 指向CWPSTRUCT型結(jié)構(gòu)體變量的指針
    return value: 未使用,返回0
    WH_MOUSE
    nCode 為HC_ACTION 或 HC_NOREMOVE
    wParam 包含鼠標(biāo)的事件消息
    lParam 指向MOUSEHOOKSTRUCT型結(jié)構(gòu)體變量的指針
    return value: 如果不處理返回0,否則返回非0值
    所以您必須查詢您的WIN32 API 指南來(lái)得到不同類型的鉤子的參數(shù)的詳細(xì)定義以及它們返回值的意義。這里還有一個(gè)問(wèn)題需要注意:所有的鉤子都串在一個(gè)鏈表上,近加入的鉤子放在鏈表的頭部。當(dāng)一個(gè)事件發(fā)生時(shí),WINDOWS將按照從鏈表頭到鏈表尾調(diào)用的順序。所以您的鉤子函數(shù)有責(zé)任把消息傳到下一個(gè)鏈中的鉤子函數(shù)。當(dāng)然您可以不這樣做,但是您好明白這時(shí)這么做的原因。在大多數(shù)的情況下,好把消息事件傳遞下去以便其它的鉤子都有機(jī)會(huì)獲得處理這一消息的機(jī)會(huì)。調(diào)用下一個(gè)鉤子函數(shù)可以調(diào)用函數(shù)CallNextHookEx。該函數(shù)的原型如下:
    CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD
    hHook 時(shí)是您自己的鉤子函數(shù)的句柄。利用該句柄可以遍歷鉤子鏈。
    nCode, wParam and lParam 您只要把傳入的參數(shù)簡(jiǎn)單傳給CallNextHookEx即可。
    請(qǐng)注意:對(duì)于遠(yuǎn)程鉤子,鉤子函數(shù)必須放到DLL中,它們將從DLL中映射到其它的進(jìn)程空間中去。當(dāng)WINDOWS映射DLL到其它的進(jìn)程空間中去時(shí),不會(huì)把數(shù)據(jù)段也進(jìn)行映射。簡(jiǎn)言之,所有的進(jìn)程僅共享DLL的代碼,至于數(shù)據(jù)段,每一個(gè)進(jìn)程都將有其單獨(dú)的拷貝。這是一個(gè)很容易被忽視的問(wèn)題。您可能想當(dāng)然的以為,在DLL中保存的值可以在所有映射該DLL的進(jìn)程之間共享。在通常情況下,由于每一個(gè)映射該DLL的進(jìn)程都有自己的數(shù)據(jù)段,所以在大多數(shù)的情況下您的程序運(yùn)行得都不錯(cuò)。但是鉤子函數(shù)卻不是如此。對(duì)于鉤子函數(shù)來(lái)說(shuō),要求DLL的數(shù)據(jù)段對(duì)所有的進(jìn)程也必須相同。這樣您就必須把數(shù)據(jù)段設(shè)成共享的,這可以通過(guò)在鏈接開關(guān)中指定段的屬性來(lái)實(shí)現(xiàn)。在MASM中您可以這么做:
    /SECTION:
, S
    已初期化的段名是.data,未初始化的段名是.bss。`加入您想要寫一個(gè)包含鉤子函數(shù)的DLL,而且想使它的未初始化的數(shù)據(jù)段在所有進(jìn)程間共享,您必須這么做:
    link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........
    S 代表該段是共享段。例子:
    一共有兩個(gè)模塊:一個(gè)是GUI部分,另一個(gè)是安裝和卸載鉤子的DLL。
    ;--------------------------------------------- 主程序的源代碼部分--------------------------------------
    .386
    .model flat,stdcall
    option casemap:none
    include \masm32\include\windows.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc
    include mousehook.inc
    includelib mousehook.lib
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib
    wsprintfA proto C :DWORD,:DWORD,:VARARG
    wsprintf TEXTEQU
    .const
    IDD_MAINDLG equ 101
    IDC_CLASSNAME equ 1000
    IDC_HANDLE equ 1001
    IDC_WNDPROC equ 1002
    IDC_HOOK equ 1004
    IDC_EXIT equ 1005
    WM_MOUSEHOOK equ WM_USER+6
    DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD
    .data
    HookFlag dd FALSE
    HookText db "&Hook",0
    UnhookText db "&Unhook",0
    template db "%lx",0
    .data?
    hInstance dd ?
    hHook dd ?
    .code
    start:
    invoke GetModuleHandle,NULL
    mov hInstance,eax
    invoke DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL
    invoke ExitProcess,NULL
    DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
    LOCAL hLib:DWORD
    LOCAL buffer[128]:byte
    LOCAL buffer1[128]:byte
    LOCAL rect:RECT
    .if uMsg==WM_CLOSE
    .if HookFlag==TRUE
    invoke UninstallHook
    .endif
    invoke EndDialog,hDlg,NULL
    .elseif uMsg==WM_INITDIALOG
    invoke GetWindowRect,hDlg,addr rect
    invoke SetWindowPos, hDlg, HWND_MOST, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW
    .elseif uMsg==WM_MOUSEHOOK
    invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
    invoke wsprintf,addr buffer,addr template,wParam
    invoke lstrcmpi,addr buffer,addr buffer1
    .if eax!=0
    invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
    .endif
    invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
    invoke GetClassName,wParam,addr buffer,128
    invoke lstrcmpi,addr buffer,addr buffer1
    .if eax!=0
    invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
    .endif
    invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
    invoke GetClassLong,wParam,GCL_WNDPROC
    invoke wsprintf,addr buffer,addr template,eax
    invoke lstrcmpi,addr buffer,addr buffer1
    .if eax!=0
    invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
    .endif
    .elseif uMsg==WM_COMMAND
    .if lParam!=0
    mov eax,wParam
    mov edx,eax
    shr edx,16
    .if dx==BN_CLICKED
    .if ax==IDC_EXIT
    invoke SendMessage,hDlg,WM_CLOSE,0,0
    .else
    .if HookFlag==FALSE
    invoke InstallHook,hDlg
    .if eax!=NULL
    mov HookFlag,TRUE
    invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
    .endif
    .else
    invoke UninstallHook
    invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
    mov HookFlag,FALSE
    invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
    invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
    invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
    .endif
    .endif
    .endif
    .endif
    .else
    mov eax,FALSE
    ret
    .endif
    mov eax,TRUE
    ret
    DlgFunc endp
    end start
    ;----------------------------------------------------- DLL的源代碼部分 --------------------------------------
    .386
    .model flat,stdcall
    option casemap:none
    include \masm32\include\windows.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\kernel32.lib
    include \masm32\include\user32.inc
    includelib \masm32\lib\user32.lib
    .const
    WM_MOUSEHOOK equ WM_USER+6
    .data
    hInstance dd 0
    .data?
    hHook dd ?
    hWnd dd ?
    .code
    DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD
    .if reason==DLL_PROCESS_ATTACH
    push hInst
    pop hInstance
    .endif
    mov eax,TRUE
    ret
    DllEntry Endp
    MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
    invoke CallNextHookEx,hHook,nCode,wParam,lParam
    mov edx,lParam
    assume edx:PTR MOUSEHOOKSTRUCT
    invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
    invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
    assume edx:nothing
    xor eax,eax
    ret
    MouseProc endp
    InstallHook proc hwnd:DWORD
    push hwnd
    pop hWnd
    invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
    mov hHook,eax
    ret
    InstallHook endp
    UninstallHook proc
    invoke UnhookWindowsHookEx,hHook
    ret
    UninstallHook endp
    End DllEntry
    ;---------------------------------------------- DLL的Makefile文件 ----------------------------------------------
    NAME=mousehook
    $(NAME).dll: $(NAME).obj
    Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib $(NAME).obj
    $(NAME).obj: $(NAME).asm
    ml /c /coff /Cp $(NAME).asm
    分析:
    該應(yīng)用程序的主窗口中包括三個(gè)編輯控件,它們將分別顯示當(dāng)前鼠標(biāo)光標(biāo)所在位置的窗口類名、窗口句柄和窗口過(guò)程的地址。還有兩個(gè)按鈕:“Hook”和“Eixt”。當(dāng)您按下Hook時(shí),應(yīng)用程序?qū)^掛鼠標(biāo)輸入的事件消息,該按鈕的文本將變成“Unhook”。當(dāng)您把鼠標(biāo)關(guān)標(biāo)滑過(guò)一個(gè)窗口時(shí),該窗口的有關(guān)消息將顯示在主窗口中。當(dāng)您按下“Unhook”時(shí),應(yīng)用程序?qū)⑿遁d鉤子。 主窗口使用一個(gè)對(duì)話框來(lái)作為它的主窗口。它自定義了一個(gè)消息WM_MOUSEHOOK,用來(lái)在主窗口和DLL之間傳遞消息。當(dāng)主窗口接收到該消息時(shí),wParam中包含了光標(biāo)所在位置的窗口的句柄。當(dāng)然這是我們做的安排。我這么做只是為了方便。您可以使用您自己的方法在主應(yīng)用程序和DLL之間進(jìn)行通訊。
    .if HookFlag==FALSE
    invoke InstallHook,hDlg
    .if eax!=NULL
    mov HookFlag,TRUE
    invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
    .endif
    該應(yīng)用程序有一個(gè)全局變量,HookFlag,它用來(lái)監(jiān)視鉤子的狀態(tài)。如果安裝來(lái)鉤子它就是TRUE,否則是FALSE。 當(dāng)用戶按下Hook按鈕時(shí),應(yīng)用程序檢查鉤子是否已經(jīng)安裝。如果還沒(méi)有的話,它將調(diào)用DLL中引出的函數(shù)InstallHook來(lái)安裝它。注意我們把主對(duì)話框的句柄傳遞給了DLL,這樣這個(gè)鉤子DLL就可以把WM_MOUSEHOOK消息傳遞給正確的窗口了。當(dāng)應(yīng)用程序加載時(shí),鉤子DLL也同時(shí)加載。時(shí)機(jī)上當(dāng)主程序一旦加載到內(nèi)存中后,DLL就立即加載。DLL的入口點(diǎn)函數(shù)載主程序的第一條語(yǔ)句執(zhí)行前就前執(zhí)行了。所以當(dāng)主程序執(zhí)行時(shí),DLL已經(jīng)初始化好了。我們載入口點(diǎn)處放入如下代碼:
    .if reason==DLL_PROCESS_ATTACH
    push hInst
    pop hInstance
    .endif
    該段代碼把DLL自己的實(shí)例句柄放到一個(gè)全局變量中保存。由于入口點(diǎn)函數(shù)是在所有函數(shù)調(diào)用前被執(zhí)行的,所以hInstance總是有效的。我們把該變量放到.data中,使得每一個(gè)進(jìn)程都有自己一個(gè)該變量的值。因?yàn)楫?dāng)鼠標(biāo)光標(biāo)停在一個(gè)窗口上時(shí),鉤子DLL被映射進(jìn)進(jìn)程的地址空間。加入在DLL缺省加載的地址處已經(jīng)加載其它的DLL,那鉤子DLL將要被映射到其他的地址。hInstance將被更新成其它的值。當(dāng)用戶按下Unhook再按下Hook時(shí),SetWindowsHookEx將被再次調(diào)用。這,它將把新的地址作為實(shí)例句柄。而在例子中這是錯(cuò)誤的,DLL裝載的地址并沒(méi)有變。這個(gè)鉤子將變成一個(gè)局部的,您只能鉤掛發(fā)生在您窗口中的鼠標(biāo)事件,這是很難讓人滿意的 。
    InstallHook proc hwnd:DWORD
    push hwnd
    pop hWnd
    invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
    mov hHook,eax
    ret
    InstallHook endp
    InstallHook 函數(shù)非常簡(jiǎn)單。它把傳遞過(guò)來(lái)的窗口句柄保存在hWnd中以備后用。接著調(diào)用SetWindowsHookEx函數(shù)來(lái)安裝一個(gè)鼠標(biāo)鉤子。該函數(shù)的返回值放在全局變量hHook中,將來(lái)在UnhookWindowsHookEx中還要使用。在調(diào)用SetWindowsHookEx后,鼠標(biāo)鉤子就開始工作了。無(wú)論什么時(shí)候發(fā)生了鼠標(biāo)事件,MouseProc函數(shù)都將被調(diào)用:
    MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
    invoke CallNextHookEx,hHook,nCode,wParam,lParam
    mov edx,lParam
    assume edx:PTR MOUSEHOOKSTRUCT
    invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
    invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
    assume edx:nothing
    xor eax,eax
    ret
    MouseProc endp
    鉤子函數(shù)首先調(diào)用CallNextHookEx函數(shù)讓其它的鉤子處理該鼠標(biāo)事件。然后,調(diào)用WindowFromPoint函數(shù)來(lái)得到給定屏幕坐標(biāo)位置處的窗口句柄。注意:我們用lParam指向的MOUSEHOOKSTRUCT型結(jié)構(gòu)體變量中的POINT成員變量作為當(dāng)前的鼠標(biāo)位置。在我們調(diào)用PostMessage函數(shù)把WM_MOUSEHOOK消息發(fā)送到主程序。您必須記住的一件事是:在鉤子函數(shù)中不要使用SendMessage函數(shù),它會(huì)引起死鎖。MOUSEHOOKSTRUCT的定義如下:
    MOUSEHOOKSTRUCT STRUCT DWORD
    pt POINT <>
    hwnd DWORD ?
    wHitTestCode DWORD ?
    dwExtraInfo DWORD ?
    MOUSEHOOKSTRUCT ENDS
    pt 是當(dāng)前鼠標(biāo)所在的屏幕位置。
    hwnd 是將接收鼠標(biāo)消息的窗口的句柄。通常它是鼠標(biāo)所在處的窗口,但是如果窗口調(diào)用了SetCapture,鼠標(biāo)的輸入將到向到這個(gè)窗口。因我們不用該成員變量而是用WindowFromPoint函數(shù)。
    wHitTestCode 指定hit-test值,該值給出了更多的鼠標(biāo)位置值。它指定了鼠標(biāo)在窗口的那個(gè)部位。該值的完全列表,請(qǐng)參考WIN32 API 指南中的WM_NCHITTEST消息。
    dwExtraInfo 該值包含了相關(guān)的信息。一般該值由mouse_event函數(shù)設(shè)定,可以調(diào)用GetMessageExtraInfo來(lái)獲得。
    當(dāng)主窗口接收到WM_MOUSEHOOK 消息時(shí),它用wParam參數(shù)中的窗口句柄來(lái)查詢窗口的消息。
    .elseif uMsg==WM_MOUSEHOOK
    invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
    invoke wsprintf,addr buffer,addr template,wParam
    invoke lstrcmpi,addr buffer,addr buffer1
    .if eax!=0
    invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
    .endif
    invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
    invoke GetClassName,wParam,addr buffer,128
    invoke lstrcmpi,addr buffer,addr buffer1
    .if eax!=0
    invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
    .endif
    invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
    invoke GetClassLong,wParam,GCL_WNDPROC
    invoke wsprintf,addr buffer,addr template,eax
    invoke lstrcmpi,addr buffer,addr buffer1
    .if eax!=0
    invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
    .endif
    為了避免重繪文本時(shí)的抖動(dòng),我們把已經(jīng)在編輯空間中線時(shí)的文本和我們將要顯示的對(duì)比。如果相同,就可以忽略掉。得到類名調(diào)用GetClassName,得到窗口過(guò)程調(diào)用GetClassLong并傳入GCL_WNDPROC標(biāo)志,然后把它們格式化成文本串并放到相關(guān)的編輯空間中去。
    invoke UninstallHook
    invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
    mov HookFlag,FALSE
    invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
    invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
    invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
    當(dāng)用戶按下Unhook后,主程序調(diào)用DLL中的UninstallHook函數(shù)。該函數(shù)調(diào)用UnhookWindowsHookEx函數(shù)。然后,它把按鈕的文本換回“Hook”,HookFlag的值設(shè)成FALSE再清除掉編輯控件中的文本。
    鏈接器的開關(guān)選項(xiàng)如下:
    Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS
    它指定.bss段作為一個(gè)共享段以便所有映射該DLL的進(jìn)程共享未初始化的數(shù)據(jù)段。如果不用該開關(guān),您DLL中的鉤子就不能正常工作了。