Chinaunix首页 | 论坛 | 博客
  • 博客访问: 129642
  • 博文数量: 37
  • 博客积分: 1490
  • 博客等级: 上尉
  • 技术积分: 326
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-01 16:38
文章分类

全部博文(37)

文章存档

2011年(1)

2010年(23)

2009年(13)

我的朋友

分类: WINDOWS

2009-12-18 17:09:30

在这一课中,我们将学习如何把一个图标放入系统托盘以及学习如何创建和使用一个弹出式菜单。

系统托盘是在任务栏上可以驻留若干图标的一块矩形区域。通常,你至少可以在它上面看到一个数字显示式时钟。你也能放置一些图标在系统托盘上。你必须执行下面这些步骤来放置一图标在系统托盘上:
填充一个 NOTIFYICONDATA结构,这个结构的成员如下:
                cbSize            这个结构的字节大小
                hwnd              窗口句柄,当鼠标事件发生在系统托盘上时,这个窗口将接收通知码。
                uID               作为图标标识的一常数。你可以取任意值,但是值必须是唯一的。因为,当有多个图标在托盘上时,
                                  你将要区分鼠标消息来自于那个图标。故这个值必须取唯一值。
                uFlags            指定这个结构的那些成员变量有效。
                hIcon             成员有效
                uCallbackMessage  成员有效
                SzTip             成员有效
                uCallbackMessage 自定义消息。当鼠标事件在托盘图标上发生时,windows将通过hwnd成员发送给指定的窗口。
                                 你可以自己创建这个消息。
hIcon      The handle of the icon you want to put into the system tray
hIcon      你想放在系统托盘上的图标的句柄。
szTip       A 64-byte array that contains the string that will be used as the tooltip text when the mouse hovers over the tray icon.
SzTip     一个64位的数组,这个数组存放的是---当鼠标停留在小图标上时,作为提示文本的字符串。
Call Shell_NotifyIcon which is defined in shell32.inc. This function has the following prototype:
调用Shell_NotifyIcon函数,这个还属定义在shell32.inc中.函数原型如下:

            Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD
dwMessage  is the type of message to send to the shell.
DwMessage 是发送给windows外壳的消息.
           NIM_ADD Adds an icon to the status area.
往状态栏上增加一个图标.
          NIM_DELETE Deletes an icon from the status area.
从状态栏上删除一图标.
          NIM_MODIFY Modifies an icon in the status area.
在状态栏中修改一图标.
    pnid  is the pointer to a NOTIFYICONDATA structure filled with proper values
Pnid  是一个指向填满特征值的NOTIFYICONDATA 结构指针。
If you want to add an icon to the tray, use NIM_ADD message, if you want to remove the icon, use NIM_DELETE.
如果你想增加一个图标在托盘上,用NIM_ADD 消息,如果你想移出一个图标,用NIM_DELETE消息。
That's all there is to it. But most of the time, you're not content in just putting an icon there. You need to be able to respond to the mouse events over the tray icon. You can do this by processing the message you specified in uCallbackMessage member of NOTIFYICONDATA structure. This message has the following values in wParam and lParam (special thanks to s__d for the info):
基本上全部的消息就是这些。但是大多数时候,你并不满足于仅仅放置一图标在这儿。你需要在鼠标事件发生在托盘图标上时能够做出响应。你能够这样做,通过在NOTIFYICONDATA结构中的uCallbackMessage成员中指定你要处理的消息。这个消息的wparam和lparam的值如下。
wParam contains the ID of the icon. This is the same value you put into uID member of NOTIFYICONDATA structure.
Wparam 包含这个图标的ID号。这是一个你放在NOTIFYICONDATA结构成员uID的值。
lParam  The low word contains the mouse message. For example, if the user right-clicked at the icon, lParam will contain WM_RBUTTONDOWN.
Lparam  低字节包含鼠标消息。例如,如果你在图标上点击了右键,lparam将包含WM_RBUTTONDOWN消息。
Most tray icon, however, displays a popup menu when the user right-click on it. We can implement this feature by creating a popup menu and then call TrackPopupMenu to display it. The steps are described below:
然而,当用户在托盘图标上点击右键时,大多数的托盘图标将显示一个弹出式菜单。我们能通过创建一个弹出式菜单来实现这一特征,然后调用TrackPopupMenu来显示它。这些步骤描述如下:
Create a popup menu by calling CreatePopupMenu. This function creates an empty menu. It returns the menu handle in eax if successful.
通过调用CreatePopupMenu函数来创建一弹出式菜单。这个函数创建一个空的菜单。如果成功,它在eax中返回一个菜单句柄。
Add menu items to it with AppendMenu, InsertMenu or InsertMenuItem.
用AppendMenu, InsertMenu 或InsertMenuItem函数来添加菜单项。
When you want to display the popup menu where the mouse cursor is, call GetCursorPos to obtain the screen coordinate of the cursor and then call TrackPopupMenu to display the menu. When the user selects a menu item from the popup menu, Windows sends WM_COMMAND message to your window procedure just like normal menu selection.
当你想在鼠标光标处显示这个菜单时,调用GetCursorPos函数来得到光标的屏幕坐标,然后调用TrackPopMenu来显示菜单。当用户从弹出式菜单上选择一个菜单项时,windows发送WM_COMMAND消息给你的窗口处理过程,就像你选择了正常的菜单一样。
Note: Beware of two annoying behaviors when you use a popup menu with a tray icon:
注意:当你在托盘图标上使用一个弹出菜单时,小心两件比较恼人的行为。
When the popup menu is displayed, if you click anywhere outside the menu, the popup menu will not disappear immediately as it should be. This behavior occurs because the window that will receive the notifications from the popup menu MUST be the foreground window. Just call SetForegroundWindow will correct it.
当弹出式菜单被显示时,如果你在菜单以外任何地方点击了鼠标,这个弹出式菜单不会像它应该做的那样立即消失不见。这个行为重现是因为接收菜单消息的窗口必须是前景窗口。仅需调用SetForegroundWindow就能纠正它。
After calling SetForegroundWindow, you will find that the first time the popup menu is displayed, it works ok but on the subsequent times, the popup menu will show up and close immediately. This behavior is "intentional", to quote from MSDN. The task switch to the program that is the owner of the tray icon in the near future is necessary. You can force this task switch by posting any message to the window of the program. Just use PostMessage, not SendMessage!
在调用SetForegroundWindow函数后,你将发现第一次弹出的菜单正常显示并且工作的很好,但是在后来,弹出式菜单弹出便立即关闭。引用MSDN这样的行为是“故意的”。为了使得弹出菜单保持住,必须要求下一个切换到的是程序的主窗口。你能通过置入任何消息给程序的窗口来强制的切换任务。但是只能用PostMessage函数而不能用SendMessage函数。
Example:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib
WM_SHELLNOTIFY equ WM_USER+5
IDI_TRAY equ 0
IDM_RESTORE equ 1000
IDM_EXIT equ 1010
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName  db "TrayIconWinClass",0
AppName    db "TrayIcon Demo",0
RestoreString db "&Restore",0
ExitString   db "E&xit Program",0
.data?
hInstance dd ?
note NOTIFYICONDATA <>
hPopupMenu dd ?
.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_APPWORKSPACE
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
           CW_USEDEFAULT,350,200,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    .while TRUE
        invoke GetMessage, ADDR msg,NULL,0,0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .endw
    mov eax,msg.wParam
    ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL pt:POINT
    .if uMsg==WM_CREATE
        invoke CreatePopupMenu
        mov hPopupMenu,eax
        invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
        invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
    .elseif uMsg==WM_DESTROY
        invoke DestroyMenu,hPopupMenu
        invoke PostQuitMessage,NULL
    .elseif uMsg==WM_SIZE
        .if wParam==SIZE_MINIMIZED
            mov note.cbSize,sizeof NOTIFYICONDATA
            push hWnd
            pop note.hwnd
            mov note.uID,IDI_TRAY
            mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
            mov note.uCallbackMessage,WM_SHELLNOTIFY
            invoke LoadIcon,NULL,IDI_WINLOGO
            mov note.hIcon,eax
            invoke lstrcpy,addr note.szTip,addr AppName
            invoke ShowWindow,hWnd,SW_HIDE
            invoke Shell_NotifyIcon,NIM_ADD,addr note
        .endif
    .elseif uMsg==WM_COMMAND
        .if lParam==0
            invoke Shell_NotifyIcon,NIM_DELETE,addr note
            mov eax,wParam
            .if ax==IDM_RESTORE
                invoke ShowWindow,hWnd,SW_RESTORE
            .else
                invoke DestroyWindow,hWnd
            .endif
        .endif
    .elseif uMsg==WM_SHELLNOTIFY
        .if wParam==IDI_TRAY
            .if lParam==WM_RBUTTONDOWN
                invoke GetCursorPos,addr pt
                invoke SetForegroundWindow,hWnd
                invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
                invoke PostMessage,hWnd,WM_NULL,0,0
            .elseif lParam==WM_LBUTTONDBLCLK
                invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
            .endif
        .endif
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .endif
    xor eax,eax
    ret
WndProc endp
end start
 
分析: 

这个程序将显示一个简单的窗口。当你按最小化按钮时,它将隐藏它自己并且放置一图标到系统托盘中。当你双击这个图标时,这个程序将恢复它原来的窗口并且将图标从系统托盘中移出。当你用右键点击它时,一个弹出式菜单被显示。你能选择恢复这个程序或者是退出程序。
   .if uMsg==WM_CREATE
        invoke CreatePopupMenu
        mov hPopupMenu,eax
        invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
        invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
当主窗口被创建时,它创建一个弹出式菜单并且添加两个菜单项。AppendMenu句法如下:
 
AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
 
            hMenu   是你想创建的那个菜单项的句柄。
            uFlags  告诉windows添加的这个菜单项是一个位图或者字符串还是以个自画的项,以及它是否可用,变灰还是禁用等。你能从win32API手册中得到完全清单。在我们的例子中,我们用MF_STRING, 它意味着这个菜单项是字符串.
           uIdNewItem 是菜单项的ID号.这是一个用户自定义的值,用这个值来表示这个菜单项.
           LpNewItem 指定菜单项的内容,依赖于uFlags成员中的值.因为我们指定在uFlags中的值是MF_STRING,lpNewItem必须包含一个指针,这个指针指向显示在弹出菜单中的字符串.
         在弹出式菜单被创建之后,主窗口耐心的等待用户按下最小化按钮.
        当窗口被最小化时,它接收到WM_SIZE消息,其中wParam参数的值是SIZE_MINIMIZED.
    .elseif uMsg==WM_SIZE
        .if wParam==SIZE_MINIMIZED
            mov note.cbSize,sizeof NOTIFYICONDATA
            push hWnd
            pop note.hwnd
            mov note.uID,IDI_TRAY
            mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
            mov note.uCallbackMessage,WM_SHELLNOTIFY
            invoke LoadIcon,NULL,IDI_WINLOGO
            mov note.hIcon,eax
            invoke lstrcpy,addr note.szTip,addr AppName
            invoke ShowWindow,hWnd,SW_HIDE
            invoke Shell_NotifyIcon,NIM_ADD,addr note
        .endif
我们用这个时机来填充NOTFYICONDATA结构体。IDI_TRAY仅是一个在源代码开始处定义的常数。你可以用你喜欢的任何值来设置它。它并不是很重要,因为你只有一个托盘图标。但是,如果你放置几个图标在系统托盘中,你需要为每一个图标定义不同的ID号。

我们在uFlags成员中设置所有的标置,因为我们指定了一个图标(NIF_ICON),我们还指定了一个自定义的消息NIF_MESSAGE和帮助文本NIF_TIP。WM_SHELLNOTIFY仅是一个定义为WM_USER+5 的自定义消息。这个实际的值并不重要,只要它是唯一的值。 在这里,我们使用的是windows登录时的图标,但是你也可以在你的程序中使用任何图标。仅需用LoadIcon函数从资源中撞在它并且把它返回的值放在hIcon成员中。最后,我们用文本填充szTip的值,这个文本的值是当鼠标在图标上时我们要显示的字符串。为了达到“最小化然后只显示图标的效果”,我们在这时隐藏掉主窗口
 
接下来,我们调用Shell_NotifyIcon 函数并用NIM_ADD消息来添加一个图标到系统托盘中。
 
现在我们的主窗口式隐藏的并且这个图标也在系统托盘中。如果你把鼠标移到它上面,你将看到我们放入szTip成员中的提示文本。下一步,如果你双击这个图标,主窗口将再次出现并且托盘图标消失 。
    .elseif uMsg==WM_SHELLNOTIFY
        .if wParam==IDI_TRAY
            .if lParam==WM_RBUTTONDOWN
                invoke GetCursorPos,addr pt
                invoke SetForegroundWindow,hWnd
                invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
                invoke PostMessage,hWnd,WM_NULL,0,0
            .elseif lParam==WM_LBUTTONDBLCLK
                invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
            .endif
        .endif
当一个鼠标事件发生在托盘图标上时,你的窗口将接收到WM_SHELLNOTIFY消息,这个消息是你放在uCallbackMessage成员变量中的自定义消息。在收到这个消息时,wparam参数包含托盘图标的ID号,lParam包含实际的鼠标消息。在上面的代码中,我们首先检查这个消息是否来自于我们感兴趣的托盘图标,如果它是,我们检查实际的鼠标消息。由于,我们仅仅对右键和双击事件感兴趣,所以我们仅处理WM_RBUTTONDOWN和WM_LBUTTONDBLCLK消息。

如果鼠标消息是WM_RBUTTONDOWN,我们调用GetCursorPos函数来得到鼠标光标在屏幕上的坐标。当函数返回时,POINT结构填充的是鼠标光标的屏幕坐标。屏幕坐标,我意思是指没有包含任何窗口边界的整个屏幕坐标。例如,如果屏幕分辨率是640*480,屏幕右下角的坐标是x=639 y=479,如果你想屏幕坐标为窗口坐标,用ScreenToClient函数。
然而,我们的目的是,我们想在当前鼠标光标的位置弹出一菜单,所以我们调用TrackPopupMenu函数,而这个函数需要的是屏幕坐标值,我们能将GetCursorPos函数返回的坐标值直接用在TrackPopupMenu函数中,句法如下:
 
TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD,  x:DWORD,  y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
 
hMenu is the handle of the popup menu to be displayed
hMenu是将要被显示的弹出菜单的句柄。
uFlags specifies the options of the function. Like where to position the menu relative to the coordinates specified later and which mouse button will be used to track the menu. In our example, we use TPM_RIGHTALIGN to position the popup menu to the left of the coordinates.
uFlags 指定一个功能选项。像相对于随后指定的坐标值,菜单放的位置和那一个鼠标按钮将被用于菜单轨迹。在我们的例子中,我们用TPM_RIGHTALIGN来决定弹出式菜单放在坐标的左边。
x and y specify the location of the menu in screen coordinates.
X 和y 指定在屏幕坐标中菜单的位置。
nReserved must be NULL  
nReserved 必须为空
hWnd is the handle of the window that will receive the messages from the menu.
hWnd 是接受从菜单接收消息的窗口句柄。
prcRect is the rectangle in the screen where it is possible to click without dismissing the menu. Normally we put NULL here so when the user clicks anywhere outside the popup menu, the menu is dismissed.
PrcRect 是屏幕中的一块矩形。如果在该矩形区域外面按下鼠标的话,菜单将消失。一般我们把该值设为NULL,这样当用户只要在菜单外面按下鼠标,菜单立即消失。
 当用户在托盘图标上双击时,我们发送WM_COMMAND消息给我们的自己的窗口,并指定IDM_RESTORE来仿效用户在弹出菜单中选择了恢复菜单项,从而恢复主窗口并且从系统托盘中移出图标。为了能够接收到双击消息,主窗口必须有CS_DBLCLKS样式。
            invoke Shell_NotifyIcon,NIM_DELETE,addr note
            mov eax,wParam
            .if ax==IDM_RESTORE
                invoke ShowWindow,hWnd,SW_RESTORE
            .else
                invoke DestroyWindow,hWnd
            .endif
当用户用选择恢复菜单项时,我们再一次调用Shell_NotifyIcon函数移出托盘图标,这个时候我们指定NIM_DELETE作为消息,下一步,我们恢复主窗口到原来的状态。如果用户选择退出菜单项,我们也从托盘中移出图标并且调用DestroyWindow来销毁窗口。
                           第二十四课: WINDOWS 钩子
 
 
我们将在这一课中学习 windows钩子。Windows钩子是非常有用的。用它们,你能截获其它进程并且还可以改变它们的行为。
Windows 钩子被认为是windows中强有力的特征之一。用它们,你可以在你自己的进程或是其它进程中捕获将要发生的事件。通过“挂钩”你告诉windows关于一个过滤函数的信息,过滤函数也叫钩子过程,每当你感兴趣的事件发生时,这个函数将被调用。
        钩子有两种类型:局部钩子和远程钩子。
           局部钩子捕获的是在你自己的进程中将要发生的事件。
           远程钩子捕获的是在其它进程中将要发生的事件。远程钩子的类型也有两种:
          基于线程的  捕获的事件是在其它进程中特殊线程将要发生的事件。简而言之,你想要观察的事件发生在一个具体的线程中,而这个线程是一特殊进程的线程。
          系统范畴的  捕获系统中所有进程的全部线程将要发生的事件。(捕捉系统中所有进程将发生的事件消息。)
 
当你安装钩子时,铭记它们带来的后果是影响系统性能。系统范畴的钩子大多数是臭名昭著的。因为所有相关事件都将被你的过滤函数过滤 ,你的系统可能会变的很慢。所以,如果你用系统范畴的钩子,你应该明智的用它并且一旦你不需要它们时就让它们脱钩。同样,你也有较高的机会让其它进程发生异常,因为你能干涉其它进程并且如果你的钩子函数出了问题,它能拉倒其它进程直到被湮没。铭记一点:性能和责任一起供给。能力伴随责任(功能强大也意味着使用时要负责任。)

在你有效的使用一个钩子之前,你必须理解一个钩子是如何工作的。当你创建了一个钩子,相应的windows在内存中就创建了一数据结构,这个数据结构包含了关于钩子的相关信息,然后把这个钩子增加到已有的钩子链表中去。新的钩子添加在老钩子的前面。当一个事件发生时,如果你安装的是局部钩子,过滤函数在你的进程中被调用,所以这是相当简单的。但是,如果它是远程钩子,系统必须为这个钩子函数在其它进程的地址空间中注入代码。并且,只有当函数驻留在DLL中时系统才能这样做。因此,如果你想使用一个远程钩子,你的钩子函数必须驻留在一个DLL中。这条规则的两个例外:工作日志钩子和工作日志回放钩子。这两个钩子的钩子过程必须驻留在安装钩子的线程中。为什么必须是这样的原因是:这两个钩子处理的是底层硬件的输入事件监听。输入事件必须是已记录的或是回放的,事件的发生也是有顺序的。如果这两个钩子的代码在DLL中,输入事件可能会散布在几个线程中间,所以就不可能知道它们发生的正确顺序。故解决的办法是:这两个钩子的钩子过程必须被安装在单一的线程中,也就是安装钩子的那个线程中。
钩子有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
 
nCode specifies the hook code
          指定钩子代码
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手册来获得关于你想安装的钩子的 参数 和返回值的详细资料
 
现在,这里还有一个关于钩子过程的小问题 。记得钩子被插入到一链表中,最近来的钩子安装在表头。当一个事件发生时,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即可
 
重点注意远程钩子: 远程钩子必须驻留在DLL文件中,这个DLL文件被映射到其它进程空间中。当windows映射这个DLL到其它进程空间中时,它的数据段不会被映射。简而言之,所有的进程共享单一的一份dll代码,但是它们自己都拥有dll的数据段节区的单独拷贝。这是一个很容易被忽视的问题。你可能会想,当你在dll 文件的数据节区中用一个变量储存一个值时,这个值将被已加载dll文件到它进程地址空间中的所有进程共享。它是简单的但不正确。在通常情况下,这种行为是称心如意的,因为每一个映射该DLL的进程都有自己的数据段拷贝,
但是,当涉及到windows钩子时,却并非如此。我们希望dll对所有的进程都是相同的,包含数据段。解决方案是:你必须标记数据节区为共享段。你能在连接的时候用连接开关指定这个节区的属性。对于masm,你可以用如下开关:
 
 /SECTION:
, 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
.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:
分析: 
 这个例子将显示一个对话框,这个对话框有三个编辑框控件,它们被与当前鼠标光标下的窗口相关联的类名,窗口句柄和窗口过程的地址填充。这里还有两个按钮, HOOK和EXIT.当你按下HOOK按钮时,程序钩住鼠标输入并设置按钮的文本为Unhook 当你在一个窗口上移动鼠标光标时,关于这个窗口的信息将显示在例子中的主窗口上。当你按下Unhook按钮时,程序清除鼠标钩子。
主程序用一个对话框作为它的主窗口。它定义了一个自定义消息,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
程序维护一个标志,HookFlag,它用来监视钩子的状态。如果钩子没有被安装它的值为FALSE,如果钩子被安装,它的值就为TRUE。当用户按下Hook按钮时,程序检查钩子是否被安装。如果它没被安装,它就调用在hook dll中的InStallHook函数来安装它。注意,我们传递主对话框的句柄作为函数的参数,这样钩子dll发送的WM_MOUSEHOOK消息就能够传递给正确的窗口。也就是,我们自己拥有的。

当程序被加载时,钩子DLL也被加载。实际上,当程序装进内存后,dlls文件被立即加载。在主程序的第一条指令执行前dll的入口点函数就被调用了。所以当主程序执行时,dll文件已经被初始化了。我们在钩子dll文件的入口点函数后面放入下面的代码:
    .if reason==DLL_PROCESS_ATTACH
        push hInst
        pop hInstance
    .endif
 
为了在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

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

首先,钩子函数要调用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  指定hit—test 值。这个值给了很多关于当前鼠标光标位置的信息。它指出鼠标光标在窗口的什么部位。为了完成列表,请你查看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 将不能正确的工作了。
阅读(1984) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~