Tutorial 24: Windows Hooks
第二十四课:windows 钩子
________________________________________
We will learn about Windows hooks in this tutorial. Windows hooks are very powerful. With them, you can poke inside other processes and sometimes alter their behaviors. 
我们将在这一课中学习 windows钩子。Windows钩子是非常有用的。 用它们,你能截获其它进程并且还可以改变它们的行为。 
Download the example here. 
Theory:
Windows hooks can be considered one of the most powerful features of Windows. With them, you can trap events that will occur, either in your own process or in other processes. By "hooking", you tell Windows about a function, filter function also called hook procedure, that will be called everytime an event you're interested in occurs. There are two types of them: local and remote hooks. 
Windows 钩子被认为是windows中强有力的特征之一。用它们,你可以在你自己的进程或是其它进程中捕获将要发生的事件。通过“挂钩”你告诉windows关于一个过滤函数的信息,过滤函数也叫钩子过程,每当你感兴趣的事件发生时,这个函数将被调用。钩子有两种类型:局部钩子和远程钩子。 
  Local hooks trap events that will occur in your own process. 
局部钩子捕获的是在你自己的进程中将要发生的事件。
  Remote hooks trap events that will occur in other process(es). There are two types of remote hooks 
远程钩子捕获的是在其它进程中将要发生的事件。远程钩子的类型也有两种:
o  thread-specific  traps events that will occur in a specific thread in other process. In short, you want to observe events in a specific thread in a specific process. 
基于线程的  捕获的事件是在其它进程中特殊线程将要发生的事件。简而言之,你想要观察的事件发生在一个具体的线程中,而这个线程是一特殊进程的线程。
o  system-wide  traps all events destined for all threads in all processes in the system. 
系统范畴的  捕获系统中所有进程的全部线程将要发生的事件。(捕捉系统中所有进程将发生的事件消息。)
When you install hooks, remember that they affect system performance. System-wide hooks are the most notorious. Since ALL related events will be routed through your filter function, your system may slow down noticeably. So if you use a system-wide hook, you should use it judiciously and unhook it as soon as you don't need it. Also, you have a higher chance of crashing the other processes since you can meddle with other processes and if something is wrong in your filter function, it can pull the other processes down to oblivion with it. Remember: Power comes with responsibility. 
当你安装钩子时,铭记它们带来的后果是影响系统性能。系统范畴的钩子大多数是臭名昭著的。因为所有相关事件都将被你的过滤函数过滤 ,你的系统可能会变的很慢。所以,如果你用系统范畴的钩子, 你应该明智的用它并且一旦你不需要它们时就让它们脱钩。同样,你也有较高的机会让其它进程发生异常,因为你能干涉其它进程并且如果你的钩子函数出了问题,它能拉倒其它进程直到被湮没。铭记一点:性能和责任一起供给。能力伴随责任(功能强大也意味着使用时要负责任。)
You have to understand how a hook works before you can use it efficiently. When you create a hook, Windows creates a data structure in memory, containing information about the hook, and adds it to a linked list of existing hooks. New hook is added in front of old hooks. When an event occurs, if you install a local hook, the filter function in your process is called so it's rather straightforward. But if it's a remote hook, the system must inject the code for the hook procedure into the address space(s) of the other process(es). And the system can do that only if the function resides in a DLL. Thus , if you want to use a remote hook, your hook procedure must reside in a DLL. There is two exceptions to this rule: journal record and journal playback hooks. The hook procedures for those two hooks must reside in the thread that installs the hooks. The reason why it must be so is that: both hooks deal with the low-level interception of hardware input events. The input events must be recorded/playbacked in the order they appeared. If the code of those two hooks is in a DLL, the input events may scatter among several threads and it is impossible to know the order of them. So the solution: the hook procedure of those two hooks must be in a single thread only i.e. the thread that installs the hooks. 
在你有效的使用一个钩子之前,你必须理解一个钩子是如何工作的。当你创建了一个钩子,相应的windows在内存中就创建了一数据结构,这个数据结构包含了关于钩子的相关信息,然后把这个钩子增加到已有的钩子链表中去。新的钩子添加在老钩子的前面。当一个事件发生时,如果你安装的是局部钩子,过滤函数在你的进程中被调用,所以这是相当简单的。但是,如果它是远程钩子,系统必须为这个钩子函数在其它进程的地址空间中注入代码。并且,只有当函数驻留在DLL中时系统才能这样做。因此,如果你想使用一个远程钩子,你的钩子函数必须驻留在一个DLL中。这条规则的两个例外:工作日志钩子和工作日志回放钩子。这两个钩子的钩子过程必须驻留在安装钩子的线程中。为什么必须是这样的原因是:这两个钩子处理的是底层硬件的输入事件监听。输入事件必须是已记录的或是回放的,事件的发生也是有顺序的。如果这两个钩子的代码在DLL中,输入事件可能会散布在几个线程中间,所以就不可能知道它们发生的正确顺序。故解决的办法是:这两个钩子的钩子过程必须被安装在单一的线程中,也就是安装钩子的那个线程中。
There are 14 types of hooks: 
钩子有14种类型:
  WH_CALLWNDPROC  called when SendMessage is called 
            当SendMessage函数被调用时。WH_CALLWNDPROCRET  called when SendMessage returns 
            当SendMessage函数返回时。
  WH_GETMESSAGE   called when GetMessage or PeekMessage is called 
当GetMessage或PeekMessage被调用时调用。
  WH_KEYBOARD  called when GetMessage or PeekMessage retrieves WM_KEYUP or WM_KEYDOWN from the message queue 
          当GetMessage或PeekMessage函数从消息队列中获得WM_KEYUP或WM_KEYDOWN消息时。
  WH_MOUSE  called when GetMessage or PeekMessage retrieves a mouse message from the message queue 
              当GetMessage或PeekMessage从消息队列中获得鼠标消息时。
  WH_HARDWARE called when GetMessage or PeekMessage retrieves some hardware message that is not related to keyboard or mouse.
当GetMessage或PeekMessage获得一些和键盘或鼠标不相 关的硬件消息时。 
  WH_MSGFILTER  called when a dialog box, menu or scrollbar is about to process a message. This hook is local. It's specifically for those objects which have their own internal message loops. 
         当对话框、菜单或滚动条要处理一个消息时。该钩子是局部的。它是特别为那些自己内部有消息循环的控件设计的。
  WH_SYSMSGFILTER  same as WH_MSGFILTER but system-wide 
          和WH_MSGFILTER一样,只不过是系统范畴的
  WH_JOURNALRECORD  called when Windows retrieves message from the hardware input queue 
          当windows从硬件输入队列中获得消息时。
  WH_JOURNALPLAYBACK  called when an event is requested from the system's hardware input queue. 
         当一个事件从系统硬件输入队列中被请求时。
  WH_SHELL  called when something interesting about the shell occurs such as when the task bar needs to redraw its button. 
         当一些有趣味的外壳事件发生时,例如,当任务栏需要重绘它的按钮时。
  WH_CBT  used specifically for computer-based training (CBT). 
         基于计算机的训练(CBT)事件发生时 
  WH_FOREGROUNDIDLE used internally by Windows. Little use for general applications 
由WINDOWS内部自己使用,一般的应用程序很少使用 
  WH_DEBUG  used to debug the hooking procedure 
           用于钩子函数的除错或调试。
Now that we know some theory, we can move on to how to install/uninstall the hooks. 
现在我们知道了一些理论,我们来学习如何安装和卸载一个钩子。
To install a hook, you call SetWindowsHookEx which has the following syntax: 
要安装一个钩子,调用SetWindowsHookEx 函数,该函数句法如下:
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD 
  HookType is one of the values listed above, e.g., WH_MOUSE, WH_KEYBOARD 
         一个上面列出的值。举例来说,WH_MOUSE WH_KEYBOARD
  pHookProc is the address of the hook procedure that will be called to process the messages for the specified hook. If the hook is a remote one, it must reside in a DLL. If not, it must be in your process. 
钩子函数的地址,它将被调用来为指定的钩子处理消息。如果是一远程钩子,它必须驻留在DLL中。否则,它必须在你的进程中。
  hInstance is the instance handle of the DLL in which the hook procedure resides. If the hook is a local one, this value must be NULL 
钩子过程驻留的DLL文件的实例句柄。如果是局部钩子,这个值必须为空。
  ThreadID  is the ID of the thread you want to install the hook to spy on. This parameter is the one that determines whether a hook is local or remote. If this parameter is NULL, Windows will interpret the hook as a system-wide remote hook that affects all threads in the system. If you specify the thread ID of a thread in your own process, this hook is a local one. If you specify the thread ID from other process, the hook is a thread-specific remote one. There are two exceptions to this rule: WH_JOURNALRECORD and WH_JOURNALPLAYBACK are always local system-wide hooks that are not required to be in a DLL. And WH_SYSMSGFILTER is always a system-wide remote hook. Actually it is identical to WH_MSGFILTER hook with ThreadID==0. 
   你想安装钩子监视的线程ID号。无论钩子是局部的还是远程的,这个参数都是确定的值。如果这个参数为空,windows将把这个钩子解释为系统范畴的远程钩子,它将影响系统中的所有线程。如果你在自己的进程中指定了一线程的ID号,这个钩子是局部钩子。如果你指定的事其它进程的线程ID号,这个钩子就是为线程指定的远程钩子。这个规则有两种特殊情况:WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK 总是局部系统范畴的钩子,它们不需要在DLL文件中。WH_SYSMSGFILTER 总是一个系统范围内的远程钩子。其实它和WH_MSGFILTER钩子类似,如果把参数ThreadID设成0的话,它们就完全一样了。
If the call is successful, it returns the hook handle in eax. If not, NULL is returned. You must save the hook handle for unhooking later.
You can uninstall a hook by calling UnhookWindowsHookEx which accepts only one parameter, the handle of the hook you want to uninstall. If the call succeeds, it returns a non-zero value in eax. Otherwise, it returns NULL. 
Now that you know how to install/uninstall hooks, we can examine the hook procedure. 
如果这个调用是成功的,它返回以钩子句柄在eax中。 如果不成功,NULL被返回。为了稍候能脱钩(卸载钩子),你必须保存这个钩子句柄。
通过调用UnhookWindowsHookEx函数,你可以卸载一个钩子。这个函数仅接收一个参数,就是你想卸载的钩子句柄。如果调用成功,它在eax中返回一个非零值,否则,它返回NULL。
现在,你知道了如何安装和卸载一个钩子,我们来分析钩子过程。 

The hook procedure will be called whenever an event that is associated with the type of hook you have installed occurs. For example, if you install WH_MOUSE hook, when a mouse event occurs, your hook procedure will be called. Regardless of the type of hook you installed, the hook procedure always has the following prototype: 
只要一个和你安装的钩子类型相关联的事件发生,钩子函数就被调用。例如,如果你安装的是WM_MOUSE钩子,当一个鼠标事件发生时,你的钩子过程将被调用。不管你安装的钩子是哪一类型,钩子过程总是下面这种原型:
HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD 
  
o  nCode specifies the hook code
          指定钩子代码
o  wParam and lParam contain additional information about the event 
wParam 和lParam 包含事件的附加信息。
HookProc is actually a placeholder for the function name. You can name it anything you like so long as it has the above prototype. The interpretation of nCode, wParam and lParam is dependent on the type of hook you install. So as the return value from the hook procedure. For example: 
HookProc 实际是一个函数名的占位符。你能用任何你喜欢的字符来命名它,只要它有上面的原型。nCode ,wParam 和 lParam 参数的解释依赖于你安装的钩子类型。条件是,从钩子过程返回的值。例如:
WH_CALLWNDPROC 
  nCode can be only HC_ACTION which means there is a message sent to a window 
         只能是HC_ACTION ,意味着这是一个发送给窗口的消息。
  wParam contains the message being sent, if it's not zero 
           如果它不为零,代表正被发送的消息。
  lParam points to a CWPSTRUCT structure 
            指向CWPSTRUCT结构的指针。
  return value: not used, return zero 
返回值:返回零,不使用。 
WH_MOUSE 
  nCode can be HC_ACTION or HC_NOREMOVE   
         可以是HC_ACTION或HC_NOREMOVE 
  wParam contains the mouse message 
             包含鼠标消息
  lParam points to a MOUSEHOOKSTRUCT structure 
          包含一个MOUSEHOOKSTRUCT结构的指针。
  return value: zero if the message should be processed. 1 if the message should be discarded. 
返回值:0 这个消息应该被处理,1 这个消息应该被抛弃。
The bottom line is: you must consult your win32 api reference for details about the meanings of the parameters and return value of the hook you want to install. 
概要是:你必须参考win32API手册来获得关于你想安装的钩子的 参数 和返回值的详细资料

Now there is a little catch about the hook procedure. Remember that the hooks are chained in a linked list with the most recently installed hook at the head of the list. When an event occurs, Windows will call only the first hook in the chain. It's your hook procedure's responsibility to call the next hook in the chain. You can choose not to call the next hook but you'd better know what you're doing. Most of the time, it's a good practice to call the next procedure so other hooks can have a shot at the event. You can call the next hook by calling CallNextHookEx which has the following prototype: 
现在,这里还有一个关于钩子过程的小问题 。记得钩子被插入到一链表中,最近来的钩子安装在表头。当一个事件发生时,windows将调用链的第一个钩子。所以你的钩子过程有责任调用下一个在链表中的钩子。你能选择不调用下一个钩子但是你最好知道你正在做什么。大多数时候, 调用下一个过程以便让其它钩子能试着去处理这事件是一个不错的习惯。
你能通过调用CallNextHookEx函数来调用下一个钩子,这个函数原型如下:

CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD 
  hHook is your own hook handle. The function uses this handle to traverse the linked list and search for the hook procedure it should call next. 
hHook 是你拥有的钩子句柄。这个函数用这个句柄在链表上搜索它下一次应该调用的钩子过程。
  nCode, wParam and lParam  you can just pass those three values you receive from Windows to CallNextHookEx. 
nCode wParam 和lParam   您只要把传入的参数简单传给CallNextHookEx即可
An important note about remote hooks: the hook procedure must reside in a DLL which will be mapped into other processes. When Windows maps the DLL into other processes, it will not map the data section(s) into the other processes. In short, all processes share a single copy of code but they will have their own private copy of the DLL's data section! 
This can be a big surprise to the unwary. You may think that when you store a value into a variable in the data section of a DLL, that value will be shared among all processes that load the DLL into their process address space. It's simply not true. In normal situation, this behavior is desirable since it provides the illusion that each process has its own copy of the DLL. But not when Windows hook is concerned. We want the DLL to be identical in all processes, including the data. The solution: you must mark the data section as shared. You can do this by specifying the section(s) attribute in the linker switch. For MASM, you need to use this switch: 
重点注意远程钩子: 远程钩子必须驻留在DLL文件中,这个DLL文件被映射到其它进程空间中。当windows映射这个DLL到其它进程空间中时,它的数据段不会被映射。简而言之,所有的进程共享单一的一份dll代码,但是它们自己都拥有dll的数据段节区的单独拷贝。这是一个很容易被忽视的问题。你可能会想,当你在dll 文件的数据节区中用一个变量储存一个值时,这个值将被已加载dll文件到它进程地址空间中的所有进程共享。它是简单的但不正确。在通常情况下,这种行为是称心如意的,因为每一个映射该DLL的进程都有自己的数据段拷贝,
但是,当涉及到windows钩子时,却并非如此。我们希望dll对所有的进程都是相同的,包含数据段。解决方案是:你必须标记数据节区为共享段。你能在连接的时候用连接开关指定这个节区的属性。对于masm,你可以用如下开关:

 /SECTION:<section name>, S
The name of the initialized data section is .data and the uninitialized data is .bss. For example if you want to assemble a DLL which contains a hook procedure and you want the uninitialized data section to be shared amoung processes, you must use the following line: 
已初始化的数据段的名字是.DATA未初始化的数据是.bss。例如,如果你想汇编一个包含钩子过程的DLL文件,并且你想未初始化数据段在所有进程间共享,你必须用下面这行:
link /section:.bss,S  /DLL  /SUBSYSTEM:WINDOWS ..........
S attribute marks the section as shared. 
S 属性使这个节区被共享。
Example:
例子:
There are two modules: one is the main program which will do the GUI part and the other is the DLL that will install/uninstall the hook. 
这里有两个模块:一个是GUI部分的主程序,一个是安装和卸载钩子的dll 文件,
;--------------------------------------------- This is the source code of the main program -------------------------------------- 
这是主程序的源代码。
.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 <wsprintfA> 
.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_TOPMOST, 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 
;----------------------------------------------------- This is the source code of the DLL -------------------------------------- 
这是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 
;---------------------------------------------- This is the makefile of the DLL ---------------------------------------------- 
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 
  
Analysis:
分析: 
The example will display a dialog box with three edit controls that will be filled with the class name, window handle and the address of the window procedure associated with the window under the mouse cursor. There are two buttons, Hook and Exit. When you press the Hook button, the program hooks the mouse input and the text on the button changes to Unhook. When you move the mouse cursor over a window, the info about that window will be displayed in the main window of the example. When you press Unhook button, the program removes the mouse hook.
 这个例子将显示一个对话框,这个对话框有三个编辑框控件,它们被与当前鼠标光标下的窗口相关联的类名,窗口句柄和窗口过程的地址填充。这里还有两个按钮, HOOK和EXIT.当你按下HOOK按钮时,程序钩住鼠标输入并设置按钮的文本为Unhook 当你在一个窗口上移动鼠标光标时,关于这个窗口的信息将显示在例子中的主窗口上。当你按下Unhook按钮时,程序清除鼠标钩子。 
The main program uses a dialog box as its main window. It defines a custom message, WM_MOUSEHOOK which will be used between the main program and the hook DLL. When the main program receives this message, wParam contains the handle of the window that the mouse cursor is on. Of course, this is an arbitrary arrangement. I decide to send the handle in wParam for the sake of simplicity. You can choose your own method of communication between the main program and the hook DLL. 
主程序用一个对话框作为它的主窗口。它定义了一个自定义消息,WM_MOUSEHOOK。这个消息将被用于主程序和钩子dll之间。当主窗口接收到这个消息时,wParam参数包含鼠标光标所在的那个窗口的句柄。当然,这个可以任意安排。出于简单,我决定发送句柄给wParam。你也能自己选择主程序和钩子dll通讯的方法。
                    .if HookFlag==FALSE 
                        invoke InstallHook,hDlg 
                        .if eax!=NULL 
                            mov HookFlag,TRUE 
                            invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText 
                        .endif 
The program maintains a flag, HookFlag, to monitor the state of the hook. It's FALSE if the hook is not installed and TRUE if the hook is installed. 
When the user presses Hook button, the program checks if the hook is already installed. If it is not, it call InstallHook function in the hook DLL to install it. Note that we pass the handle of the main dialog as the parameter of the function so the hook DLL can send the WM_MOUSEHOOK messages to the right window i.e. our own. 
程序维护一个标志,HookFlag,它用来监视钩子的状态。如果钩子没有被安装它的值为FALSE,如果钩子被安装,它的值就为TRUE。当用户按下Hook按钮时,程序检查钩子是否被安装。如果它没被安装,它就调用在hook dll中的InStallHook函数来安装它。注意,我们传递主对话框的句柄作为函数的参数,这样钩子dll发送的WM_MOUSEHOOK消息就能够传递给正确的窗口。也就是,我们自己拥有的。

When the program is loaded, the hook DLL is loaded too. Actually, DLLs are loaded immediately after the program is in memory. The DLL entrypoint function is called before the first instruction in the main program is execute even. So when the main program executes the DLL(s) is/are initialized. We put the following code in the DLL entrypoint function of the hook DLL: 
当程序被加载时,钩子DLL也被加载。实际上,当程序装进内存后,dlls文件被立即加载。在主程序的第一条指令执行前dll的入口点函数就被调用了。所以当主程序执行时,dll文件已经被初始化了。我们在钩子dll文件的入口点函数后面放入下面的代码: 
    .if reason==DLL_PROCESS_ATTACH 
        push hInst 
        pop hInstance 
    .endif 
The code just saves the instance handle of the hook DLL itself to a global variable named hInstance for use within the InstallHook function. Since the DLL entrypoint function is called before other functions in the DLL are called , hInstance is always valid. We put hInstance in .data section so that this value is kept on per-process basis. 
Since when the mouse cursor hovers over a window, the hook DLL is mapped into the process. Imagine that there is already a DLL that occupies the intended load address of the hook DLL, the hook DLL would be remapped to another address. The value of hInstance will be updated to those of the new load address. When the user presses Unhook button and then Hook button, SetWindowsHookEx will be called again. However, this time, it will use the new load address as the instance handle which will be wrong because in the example process, the hook DLL's load address hasn't been changed. The hook will be a local one where you can hook only the mouse events that occur in your own window. Hardly desirable. 
为了在InstallHook函数中使用,该钩子dll只不过是保存它的实例句柄在名字为hInstance的全局变量中。因为在dll中的其它函数被调用之前,dll的入口函数就被调用,所以hInstance总是有效的。我们放置hInstance在数据节区中使得每一个进程都有自己一个该变量的值。因为当鼠标光标停留在一个窗口上时,钩子dll被映射进进程的地址空间中。设想一下,钩子dll加载的地址空间已经被另一个dll占用,钩子dll将被重新映射在其它的地址空间。hInstance的值将被更新成那些新加载的地址。当用户按下Unhook按钮或者是Hook按钮,SetWindowsHookEx将被再一次调用。然而,这一次,它将用新加载的地址作为实例句柄,因为在例子进程中,这个钩子dll的地址空间没有被改变,故新加载的地址是错误的。这个钩子是局部变量的一种,它仅能钩挂发生在你窗口中的鼠标事件。这是很难让人满意的。
InstallHook proc hwnd:DWORD 
    push hwnd 
    pop hWnd 
    invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL 
    mov hHook,eax 
    ret 
InstallHook endp 
The InstallHook function itself is very simple. It saves the window handle passed as its parameter to a global variable named hWnd for future use. It then calls SetWindowsHookEx to install a mouse hook. The return value of SetWindowsHookEx is stored in a global variable named hHook for use with UnhookWindowsHookEx.
InstallHook函数本身是非常简单的。它把作为它的参数传递过来的窗口句柄保存在全局变量hWnd中,已备将来使用。然后调用SetWindowsHookEx函数来安装一个鼠标钩子。为了让UnhookWindowsHookEx将来使用,SetWindowsHookEx函数的返回值被储存在一个名为hHook的全局变量中, 
After SetWindowsHookEx is called, the mouse hook is functional. Whenever a mouse event occurs in the system, MouseProc ( your hook procedure) is called. 
在SetWindowsHookEx被调用之后,鼠标钩子就工作了。只要在系统中有鼠标事件发生,MouseProc(你的钩子过程)就被调用。
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 
The first thing it does is to call CallNextHookEx to give the other hooks the chance to process the mouse event. After that, it calls WindowFromPoint function to retrieve the handle of the window at the specified screen coordinate. Note that we use the POINT structure in the MOUSEHOOKSTRUCT structure pointed to by lParam as the current mouse coordinate. After that we send the window handle to the main program via PostMessage with WM_MOUSEHOOK message. One thing you should remember is that: you should not use SendMessage inside the hook procedure, it can cause message deadlock. PostMessage is recommended. The MOUSEHOOKSTRUCT structure is defined below: 
首先,钩子函数要调用CallNextHookEx来让其它钩子有机会处理鼠标事件。在这之后,然后,调用WindowFromPoint函数来得到给定屏幕坐标位置处的窗口句柄。注意,我们用lParam参数指向的MOUSEHOOKSTRUCT结构体指针中的POINT指针作为鼠标的当前坐标。在我们通过PostMessage发送窗口句柄和WM_MOUSEHOOK给主窗口之后。你应该记住的一件事是:你不能在钩子过程中SendMessage函数,它能引起消息死锁。我们建议你使用PostMessage。MOUSEHOOKSTRUCT结构体定义如下:
MOUSEHOOKSTRUCT STRUCT DWORD 
  pt            POINT <> 
  hwnd          DWORD      ? 
  wHitTestCode  DWORD      ? 
  dwExtraInfo   DWORD      ? 
MOUSEHOOKSTRUCT ENDS 
  
  pt is the current screen coordinate of the mouse cursor 
pt是鼠标光标当前屏幕坐标。
  hwnd is the handle of the window that will receive the mouse message. It's usually the window under the mouse cursor but not always. If a window calls SetCapture, the mouse input will be redirected to that window instead. Because of this reason, I don't use the hwnd member of this structure but choose to call WindowFromPoint instead. 
Hwnd 是将要接受鼠标消息的窗口句柄。它通常是在鼠标光标下的窗口,但不总都是。如果一个窗口调用SetCapture,鼠标输入将被重定向到这个窗口。由于这个原因,我并不使用这个结构的hwnd成员但是我们用WindowFromPoint调用代替。
  wHitTestCode specifies the hit-test value. The hit-test value gives more information about the current mouse cursor position. It specifies on what part of window the mouse cursor is. For complete list, check your win32 api reference under WM_NCHITTEST message. 
wHitTestCode  指定hittest 值。这个值给了很多关于当前鼠标光标位置的信息。它指出鼠标光标在窗口的什么部位。为了完成列表,请你查看win32api 手册中的WM_NCHITTEST消息。
  dwExtraInfo contains the extra information associated with the message. Normally this value is set by calling mouse_event and retrieved by calling GetMessageExtraInfo. 
DwExtraInfo  包含和消息有关的额外信息。通常这个值被调用的鼠标事件设置,还可以调用GetMessageExtraInfo 获得。
When the main window receives WM_MOUSEHOOK message, it uses the window handle in wParam to retrieve the information about the window. 
当主窗口接受到WM_MOUSEHOOK消息时,它用在wParam参数中的窗口句柄来检索关于窗口的信息。
    .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 
To avoid flickers, we check the text already in the edit controls and the text we will put into them if they are identical. If they are, we skip them. 
为了避免闪烁,我们检查已经在编辑控件中的文本和我们将要显示的文本是否相同。如果相同,我们就忽略它们。
We retrieve the class name by calling GetClassName, the address of the window procedure by calling GetClassLong with GCL_WNDPROC and then format them into strings and put them into the appropriate edit controls. 
我们调用GetClassName函数来获取类名,我们传入GCL_WNDPROC然后调用GetClassLong函数来获得窗口过程的地址,然后格式化字符串并把它们放入到合适的编辑控件中。
                        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 
When the user presses Unhook button, the program calls UninstallHook function in the hook DLL. UninstallHook just calls UnhookWindowsHookEx. After that, it changes the text of the button back to "Hook", HookFlag to FALSE and clears the content of the edit controls. 
当用户按下Unhook按钮时,程序调用在钩子dll中的UninstallHook函数,UninstallHook仅仅是调用UnhookWindowsHookEx。在这之后,他将按钮的文本改为Hook,并置HookFlag标志为FALSE然后清除编辑控件中的内容。
Note the linker switch in the makefile. 
注意:makefile中的连接标志如下:

        Link /SECTION:.bss,S  /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS 
It specifies .bss section as a shared section to make all processes share the same uninitialized data section of the hook DLL. Without this switch, your hook DLL will not function correctly. 
它指定.bss段作为一个共享段以便让所有的进程共享同一个在Hook 动态链接库中的未初始化数据段。没有了这个开关,你的钩子dll 将不能正确的工作了。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-3-1