千家论坛_弱电智能化技术与工程讨论(建筑智能,家居智能,人工智能)

家居布线与家庭组网
收藏本版 (7) |订阅

家居布线与家庭组网 今日: 2376 |主题: 4096

发新帖
打印 上一主题 下一主题

[分享]WIN32 汇编...

[复制链接]
0758河台小赵 发布于: 2004-9-14 14:59 6744 次浏览 37 位用户参与讨论
跳转到指定楼层

新浪微博达人勋

千家认证

21#
0758河台小赵 发表于 2004-9-14 15:55:00
通用控件本课中我们将学习什么是通用控件和如何使用它们。 理论:WIN95相对于WIN3X有几个加强的用户界面控件。其实在WIN95正式发行前这些控件就在使用,譬如:状态条、工具条等。以前程序员要自己去编程使用它们,现在微软已经把它们包含到了WIN9X和WINNT中了。 Toolbar ---工具条 Tooltip ---提示文本 Status bar ---状态条 Property sheet ---属性页单 Property page ---属性页 Tree view ---树型视图 List view ---列表视图 Animation ---动画 Drag list ---能够处理Drag-Drop的列表框 Header --- Hot-key ---热键 Image list ---图象链表 Progress bar ---进程状态条 Right edit --- Tab ---跳格表 Trackbar ---跟踪条 Up-down ---滚动条 因为通用控件的数量非常多,把它们全部装入内存并注册它们是非常浪费内存的。除了“RTF文本编辑”控件外其他控件的可执行代码都放在comctl32.dll中,这样其他的应用程序就可以使用它们了。“RTF文本编辑”控件在richedXX.dll中,由于该控件非常的复杂,所以也比其它控件大。 要加载comctl32.dll可以在您的应用程序中调用函数InitCommonControls。InitCommonControls函数是动态链接库comctl32.dll中的一个函数,只要在您的程序中的任意地方引用了该函数就、会使得WINDOWS的程序加载器PE Loader加载该库。函数InitCommonControls其实只有一条指令“ret”,它的唯一目的是为了使得在调用了个该函数的应用程序的可执行文件的PE头中的“引入”段中包含有comctl32.dll,这样无论什么时候该应用程序都会为您加载该库。所以真正初始化的工作是在该库的入口点处做的,在这里会注册所有的通用控件类,然后所有的通用控件就可以在这些类上进行创建,这就象创建其它的子窗口控件一样。 RTF文本编辑控件则不同。如果您要使用它,就必须调用LoadLibrary函数来动态加载,并调用FreeLibrary来动态地卸载。 现在我们学习如何创建这些通用控件。您可以用资源编辑器把它们放到一个对话框中,或者您也可以自己调用相关的函数来手动创建它们。几乎所有的通用控件都是调用函数CreateWindowEx或CreateWindow来创建的,您只要在其中传递通用控件的类名即可。有一些通用控件有一些特别的创建函数,但是其实这些函数在内部都调用了CreateWindowEx,只是包装后的函数更方便使用而已。经过包装的函数有: CreateToolbarEx CreateStatusWindow CreatePropertySheetPage PropertySheet ImageList_Create 为了创建通用控件您必须要知道它们的类名,我们把类名列于如下: 类名 通用控件 ToolbarWindow32Toolbartooltips_class32Tooltipmsctls_statusbar32Status barSysTreeView32Tree viewSysListView32List viewSysAnimate32AnimationSysHeader32Headermsctls_hotkey32Hot-keymsctls_progress32Progress barRICHEDITRich editmsctls_updown32Up-downSysTabControl32Tab

Property sheets、property pages和image list控件有它们自己的创建函数。Drag list其实是可以伸缩的listbox控件,所以它没有自己的类名。上面的类名是VC++的资源编辑器提供的,它们和Borland公司的WIN32 API指南中提出的不一样,和Petzold的书《Programming Windows 95》也不一样。可以肯定的是我们上面列出的类名绝对准确。 这些通用控件可以有通用的窗口类的一些风格,譬如WS_CHILD等。它们当然还有其他的特殊风格,譬如树型视图控件就有TVS_XXXXX风格,列表控件就有LVS_xxxx风格。具体的最好查找有关的WIN32 API函数指南。 既然我们已经知道了如何创建一个通用控件,我们就可以讨论这些通用控件之间以及和它们的父窗口之间是如何通讯的了。不象子窗口控件,通用控件在某些状态发生变化时不是通过发送WM_COMMAND而是发送WM_NOTIFY消息和父窗口通讯的。父窗口可以通过发送消息来控制子窗口的行为。对于那些新的通用控件,还有一些新的消息类型。您可以参考您的WIN32 API手册。

在下面的例子中我们将要实验一下进度条和状态条。

例子代码:.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\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.const IDC_PROGRESS equ 1 ; control IDs IDC_STATUS equ 2 IDC_TIMER equ 3

.data ClassName db "CommonControlWinClass",0 AppName db "Common Control Demo",0 ProgressClass db "msctls_progress32",0 ; the class name of the progress bar Message db "Finished!",0 TimerID dd 0

.data? hInstance HINSTANCE ? hwndProgress dd ? hwndStatus dd ? CurrentStep dd ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls

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 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,CW_USEDEFAULT,CW_USEDEFAULT,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 .if uMsg==WM_CREATE invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\ WS_CHILD+WS_VISIBLE,100,\ 200,300,20,hWnd,IDC_PROGRESS,\ hInstance,NULL mov hwndProgress,eax mov eax,1000 ; the lParam of PBM_SETRANGE message contains the range mov CurrentStep,eax shl eax,16 ; the high range is in the high word invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0 invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS mov hwndStatus,eax invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer mov TimerID,eax .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .if TimerID!=0 invoke KillTimer,hWnd,TimerID .endif .elseif uMsg==WM_TIMER ; when a timer event occurs invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 ; step up the progress in the progress bar sub CurrentStep,10 .if CurrentStep==0 invoke KillTimer,hWnd,TimerID mov TimerID,0 invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SendMessage,hwndStatus,SB_SETTEXT,0,0 invoke SendMessage,hwndProgress,PBM_SETPOS,0,0 .endif .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start 分析: invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls 我故意把函数InitCommonControls放到ExitProcess后,这样就可以验证调用该函数仅仅是为了在我们程序的可执行文件的PE头中的引入段中放入引用了comctl32.dll的信息。您可以看到,即使该函数什么都没有做,我们的通用控件对话框依旧可以正常工作。 .if uMsg==WM_CREATE invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,\ WS_CHILD+WS_VISIBLE,100,\ 200,300,20,hWnd,IDC_PROGRESS,\ hInstance,NULL mov hwndProgress,eax 在这里我们创建了通用控件。注意CreateWindowEx函数中的参数hWnd是父窗口的句柄。另外它也指定了通用控件的ID号。因为我们直接使用控件的窗口句柄,所以就没有使用该ID号。所有的窗口都必须具有WS_CHILD风格。 mov eax,1000 mov CurrentStep,eax shl eax,16 invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0 在创建了进度条后我们先设定它的范围。缺省的范围是0-100。如果您不满意,可以重新设置,这通过传递PBM_SETRANGE消息来实现。参数lParam中包含了范围值,其中底字和高字分别是范围的起始和终了的值。您可以指定进度条每移动一格的步长。本例子中把步长设置成10,意味着每发送一次PBM_STEPIT消息给进度条,它的显示指针就会移动10。当然您可以调用PBM_SETPOS 来直接设定进度条上的指针的位置。用该消息您可以更方便地设定进度条了。 invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS mov hwndStatus,eax invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer mov TimerID,eax 下面我们调用CreateStatusWindow来创建状态条。这个调用很好理解,无需我多解释。在状态条创建后我们创建一个计时器。在本例中我们每隔100毫秒就更新一次进度条。下面时创建记时器的函数原型: SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD hWnd : 父窗口的句柄。 TimerID : 计时器的ID号。您可以指定一个唯一的非零值。 TimerInterval : 以毫秒计的时间间隔。 lpTimerProc : 计时器回调函数的地址。每当时间间隔到了的时候,该函数就会被系统调用。如果该值为NULL,计时器就会把WM_TIMER消息发送到父窗口。

如果SetTimer调用成功的话就会返回计时器的ID号值,否则返回0。这也是为什么计时器的ID号必须为非零值的原因。 .elseif uMsg==WM_TIMER invoke SendMessage,hwndProgress,PBM_STEPIT,0,0 sub CurrentStep,10 .if CurrentStep==0 invoke KillTimer,hWnd,TimerID mov TimerID,0 invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SendMessage,hwndStatus,SB_SETTEXT,0,0 invoke SendMessage,hwndProgress,PBM_SETPOS,0,0 .endif 当指定的时间到了的时候,计时器将发送WM_TIMER消息。您可以在处理该消息时作适当的处理。本例中我们将更新进度条,并检查进度条是否超过最大的值。如果超过了的话,我们通过发送SB_SETTEXT消息来在状态条中设置文本。这时,弹出一个对话框,当用户关闭掉对话框后,我们去除掉进度条和状态条中的文本。

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

22#
xsxiaoshan 发表于 2004-9-14 15:56:00

看了黑侠大哥文章 .不禁让人汗颜. 简直是猾天下之大稽啊

他说没有win32 汇编的书,开什么国际玩笑.

我看是上网上疯了.分不清东南西北了.

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

23#
0758河台小赵 发表于 2004-9-14 15:56:00
窗口子类化

在这一讲,我们将学习什么是窗口子类化和怎样按你所想要的方式方便地使用它。

理论:

如果你曾经在 Windows 环境下编过程序,有时候就会发现:有一个现成的窗口,几乎有你所需要的全部功能,但还不完全一样(否则就没有必要讲这一节了)。你曾遇到过这样的处境吗,如果你需要一个具有过滤特殊字符功能的 Edit 控件。当然最直接的方法就是自己用代码来实现,但这的确是一个费时又很困难的任务,而窗口子类化就可以用来做这种事情。

窗口子类化允许你接管被子类化的窗口,使你对它有绝对的控制权。举个例子了来阐明一下:例如你需要一个只接受十六进制数字输入的文本编辑框,如果使用一个简单的 Edit控件,当用户输入十六进制以外的字符时,你既不知道也无计可施。也就是说,当用户进文本框中输入字符串 "zb+q*" 时,如果除了拒绝接受整个字符串以外几乎什么也不能做,至少这显得特别不专业。重要的是,你需要具有输入检测的能力,即每当用户输入一个字符到编辑框中时要能检测这个字符。

现在来解释实现细节:当用户往文本框中输入字符时,Windows 会给Edit控件的窗口函数发送 WM_CHAR 消息。这个窗口函数本身寄生于 Windows 中,因此不能直接修改它。但是我们可以重定向这个消息使之发送到我们自己编写的窗口处理函数。如果自定义窗口要处理这个消息那就可以处理它,如果不处理就可以把这个消息转发到它原来窗口处理函数。通过这种方式,自定义的窗口处理函数就把它自己插入到 Windows 系统和 Edit 控件之间。

看下面的流程: 窗口子类化之前 Windows ==>Edit 控件的窗口处理函数。

子类化之后 Windows ==>自定义的窗口处理函数==> Edit 控件的窗口处理函数。

注意子类化并不局限于控件,可以子类化任何窗口,现在我们要把精力集中到怎样实现子类化一个窗口上。让我们想想Windows 怎样知道 Edit 控件的窗口处理函数放在什么地方。猜的?…肯定不是。原来 WNDCLASSEX 结构的成员 lpfnWndProc 指出了窗口函数地址。如果能用自己编写的窗口函数的地址来替换这个成员变量,那 Windows 不就把消息发到自定义的窗口函数了吗! 我们通过调用函数SetWindowLong 来实现这个任务,此函数的原型为:

SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD

hWnd = 将要实施子类化的窗口的句柄 nIndex = 函数了功能索引 GWL_EXSTYLE 设置窗口的扩展风格. GWL_STYLE 设置新的窗口风格 GWL_WNDPROC 设置新的窗口处理函数地址 GWL_HINSTANCE 设置新的应用程序句柄 GWL_ID 设置新的窗口标识 GWL_USERDATA 设置一个与这个窗口相关的给用户使用的32位的数据 dwNewLong = 用来更新的数据

我们的工作还是比较简单的:

写一个窗口函数用于处理发给 Edit 控件的消息。 用参数GWL_WNDPROC调用SetWindowLong 函数,如果调用成功那么返回值就是与调用功能相联系的一个32位的整数

在我们的程序中,返回值就是原先窗口函数的地址。我们要保存这个值以便以后使用。 记住:有一些我们不处理的消息,需要把它们派遣给原来的窗口函数来处理,这就用到另外一个函数 CallWindowProc, 函数原型为:

CallWindowProc PROTO lpPrevWndFunc:DWORD, hWnd:DWORD, Msg:DWORD, wParam:DWORD, lParam:DWORD

lpPrevWndFunc = 窗口原来函数的地址. 剩下的四个参数就是发给自定义函数的参数,直接把它们传给函数 CallWindowProc 就行了。

代码举例:

.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\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD .data ClassName db "SubclassWinClass",0 AppName db "Subclassing Demo",0 EditClass db "EDIT",0 Message db "You pressed Enter in the text box!",0 .data? hInstance HINSTANCE ? hwndEdit dd ? OldWndProc 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 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 .if uMsg==WM_CREATE invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ 20,300,25,hWnd,NULL,\ hInstance,NULL mov hwndEdit,eax invoke SetFocus,eax ;----------------------------------------- ; Subclass it! ;----------------------------------------- invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc mov OldWndProc,eax .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_CHAR mov eax,wParam .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK .if al>="a" && al<="f" sub al,20h .endif invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam ret .endif .elseif uMsg==WM_KEYDOWN mov eax,wParam .if al==VK_RETURN invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SetFocus,hEdit .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .endif .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .endif xor eax,eax ret EditWndProc endp end start

分析:

invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc mov OldWndProc,eax

在创建 Edit 控件后,通过调用 SetWindowLong 把原来的窗口函数地址替换为自定义函数的地址,从而对它实施了窗口子类化,要注意 为了调用函数 CallWindowProc,我们存储了原窗口函数地址,自已编写的EditWndProc 仅仅是个普普通通的窗口函数。当然也可以再调用一次 SetWindowLong 函数来存储这个32位的值,

invoke SetWindowLong ,hwndEdit,GWL_USERDATA,eax 。

当然用的时候就要调用GetWindowLong 来取回这个值。

.if uMsg==WM_CHAR mov eax,wParam .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK .if al>="a" && al<="f" sub al,20h .endif invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam ret .endif 在函数 EditWndProc 中,我们自己处理了WM_CHAR消息: 如果输入的字符是'0'--'9'、'A'-'F'或者是'a'--'f'就接受,并且把此消息转发给原窗口函数,其中若输入的是小写的'a'--'f'就把它变为大写。如果输入的不是十六进制字符,就丢掉它,并且也不转发此消息。因此当输入是非十六进制字符时,这个字符就不会显示在 Edit 控件中。 .elseif uMsg==WM_KEYDOWN mov eax,wParam .if al==VK_RETURN invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SetFocus,hEdit .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .end 在这里我们通过处理 回车(Enter) 键进一步示范了子类化的能力。EditWndProc 通过检查 WM_KEYDONW 消息来判断是否是 回车键,若是显示提示消息框,否则转发此消息。 你可以用窗口子类化来控制另外的窗口,这是必须掌握的十分有用的技术之一
回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

24#
0758河台小赵 发表于 2004-9-14 15:57:00
超类化

在这一讲我们将学习什么是超类化以及它有什么作用;同时你还会学到怎样在自己的窗口中用Tab键在控件中切换这一技巧。

理论:

在你的程序生涯中你肯定遇到过这样的情况,你需要一系列的控件,但它们之间却只有一点点的不同。例如,你可能需要10个只接受数字的 Edit 控件,当然你可以通过多种方法来达到这个目的。

创建自己的类并用它实例化为那些控件 创建那些 Edit 控件并把它们全部子类化 超类化Edit 控件

第一种方法太乏味了,因为你必须自己实现Edit 控件的每个功能,但这项工作不是轻松就能完成的。第二种方法好于第一种,但仍然要做许多工作,子类化几个Edit 控件还可以接受,但若要子类化十几二十个,这项工作简直就是一场恶梦。在这种情况下就应该使用超类化这个技巧,它是用于控制某一个特定窗口类的特殊方法。通过这种控制就可以修改窗口类的特性使之符合你的要求,然后再创建那一堆控件就可以了。

超类化有如下几个步骤:

通过调用 GetClassInfoEx 来获得想要进行超类化操作的窗口类的信息。函数GetClassInfoEx 需要一个指向 WNDCLASSEX 结构的指针,用于当成功返回时填入窗口类的信息。 按需要修改 WNDCLASSEX 结构的成员,其中有两个成员必须修改: hInstance 存放程序的实例句柄 lpszClassName 指向一个新类名的指针 不必修改成员 lpfnWndProc,但大多数情况下还是需要的。但要记住如果要使用函数 CallWindowProc 调用老窗口的过程,那就必须保存成员 lpfnWndProc 的原值。 注册修改完的 WNDCLASSEX 结构,得到一个具有旧窗口类某些特性的新窗口类。 用新窗口类创建窗口

如果要创建具有相同特性的多个控件,超类化就比子类化要好。

举例:

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WM_SUPERCLASS equ WM_USER+5 WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data ClassName db "SuperclassWinClass",0 AppName db "Superclassing Demo",0 EditClass db "EDIT",0 OurClass db "SUPEREDITCLASS",0 Message db "You pressed the Enter key in the text box!",0

.data? hInstance dd ? hwndEdit dd 6 dup(?) ;存放6个窗口句柄的数组 OldWndProc 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 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+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,350,220,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 uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL wc:WNDCLASSEX .if uMsg==WM_CREATE mov wc.cbSize,sizeof WNDCLASSEX invoke GetClassInfoEx,NULL,addr EditClass,addr wc push wc.lpfnWndProc pop OldWndProc mov wc.lpfnWndProc, OFFSET EditWndProc push hInstance pop wc.hInstance mov wc.lpszClassName,OFFSET OurClass invoke RegisterClassEx, addr wc xor ebx,ebx mov edi,20 .while ebx<6 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ edi,300,25,hWnd,ebx,\ hInstance,NULL mov dword ptr [hwndEdit+4*ebx],eax add edi,25 inc ebx .endw invoke SetFocus,hwndEdit .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp

EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_CHAR mov eax,wParam .if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK ;处理字符0~9,A~F,a~f,这几个十六进制数 .if al>="a" && al<="f" sub al,20h 如果是字符a~f,则把它们变为大写 .endif invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam ret .endif .elseif uMsg==WM_KEYDOWN mov eax,wParam .if al==VK_RETURN invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION invoke SetFocus,hEdit .elseif al==VK_TAB invoke GetKeyState,VK_SHIFT test eax,80000000 .if ZERO? invoke GetWindow,hEdit,GW_HWNDNEXT .if eax==NULL invoke GetWindow,hEdit,GW_HWNDFIRST .endif .else invoke GetWindow,hEdit,GW_HWNDPREV .if eax==NULL invoke GetWindow,hEdit,GW_HWNDLAST .endif .endif invoke SetFocus,eax xor eax,eax ret .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .endif .else invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam ret .endif xor eax,eax ret EditWndProc endp end start

分析

这个程序创建了一个在其客户区有六个被修改的 Edit 控件的简单窗口,这些 Edit控件只接受十六进制的数字。实际上,这个例子是通过修改窗口了类化的例子得来的。这个程序开始和其它程序一样,有趣的部分出现在主窗口被创建的时候:

.if uMsg==WM_CREATE mov wc.cbSize,sizeof WNDCLASSEX invoke GetClassInfoEx,NULL,addr EditClass,addr wc

必须用想进行超类化操作的类数据填充 WNDCLASSEX 结构,在我们的例子中就是类 Edit ,记住在调用函数 GetClassInfoEx 之前必须填写成员 cbSize,否则函数调用 GetClassInfoEx不会在 WNDCLASSEX 结构中填入正确的返回值。成功返回后,变量 wc中保存的就是想要创建一个新类所需要的所有信息。

push wc.lpfnWndProc pop OldWndProc mov wc.lpfnWndProc, OFFSET EditWndProc push hInstance pop wc.hInstance mov wc.lpszClassName,OFFSET OurClass

现在必须修改变量 wc 的一些属性:第一个要修改的就是指向窗口过程的指针。因为在新窗口过程中函数 CallWindowProx 要用到老窗口过程,因此得把它保存到一个变量中以便使用。这个技巧和在子类化中用到的一样,只不过不是调用 SetWindowLong 而是直接修改 WNDCLASSEX 结构罢了。接下来必须得为这个新类取个名字。

invoke RegisterClassEx, addr wc

当所有这些都完成时,注册这个新类就会得到一个具有旧类某些特征的新类了。

xor ebx,ebx mov edi,20 .while ebx<6 invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\ WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ edi,300,25,hWnd,ebx,\ hInstance,NULL mov dword ptr [hwndEdit+4*ebx],eax add edi,25 inc ebx .endw invoke SetFocus,hwndEdit

注册完新类就可以创建基于它的窗口了: 在上面的程序片断中,用寄存器 ebx 来保存已创建的窗口数目,用寄存器 edi 来保存窗口左上角的 y 坐标。创建一个新窗口时,把它的句柄保存在一个双字的数组中,当创建完所有的窗口后,设定输入焦点为所创建的第一个窗口。

这时已经有6个只能接受十六进制数字的 edit 窗口控件了,替换的窗口过程处理了字符过滤,这实际上和在子类化中的例子是一样的。但不必做子类化那些窗口的额外工作了。

在此程序中,通过使用 Tabs 键来在各个 Edit 控件中切换来使得这个程序更加有趣。一般来说,如果使用对话框,对话框管理器会处理好所有这些问题,即: 按下 Tabs 输入焦点切换到下一个控件窗口中,按下 Shift-Tabs 输入焦点切换到上一个控件窗口中;但一个简单的窗口不具有这个功能,必须子类化它们以处理 Tabs 键。在这个例子中,不必一个一个去子类化已经进行过超类化操作的这些控件,可以使用一种集中控制切换策略。

.elseif al==VK_TAB invoke GetKeyState,VK_SHIFT test eax,80000000 .if ZERO? invoke GetWindow,hEdit,GW_HWNDNEXT .if eax==NULL invoke GetWindow,hEdit,GW_HWNDFIRST .endif .else invoke GetWindow,hEdit,GW_HWNDPREV .if eax==NULL invoke GetWindow,hEdit,GW_HWNDLAST .endif .endif invoke SetFocus,eax xor eax,eax ret

上面是摘自于 EditWndClass 过程的程序片断,它检查用户是否按下了 Tabs 键,若是就调用函数 GetKeyState 来检查 SHIFT 键是否也被同时按下了。函数 GetKeyState 在寄存器 eax 中设立一个返回值,用于判断某个特定的键是否被按下了,若按下了,则把 eax 的的最高位置1,否则把最高位清0。所以只要用 80000000h 来测试返回值就行了,若最高位是1则说明用户按下了 SHIFT-Tabs,这需要单独处理;否则说明只按下 Tabs 键,调用函数 GetWindow 来获得 hEdit 所指向窗口的下一个窗口句柄,若该函数返回 NULL ,说明这是当前窗口是窗口链中最后一个窗口了,应该通过以参数 GW_HWNDFIRST 调用函数 GetWindow 来卷回到窗口链中的第一个窗口控件。SHIFT-Tabs 的处理过程和这正好相反。

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

25#
0758河台小赵 发表于 2004-9-14 15:58:00
本课中我们将要学习WINDOWS钩子函数的使用方法。WINDOWS钩子函数的功能非常强大,有了它您可以探测其它进程并且改变其它进程的行为。 理论:WINDOWS的钩子函数可以认为是WINDOWS的主要特性之一。利用它们,您可以捕捉您自己进程或其它进程发生的事件。通过“钩挂”,您可以给WINDOWS一个处理或过滤事件的回调函数,该函数也叫做“钩子函数”,当每次发生您感兴趣的事件时,WINDOWS都将调用该函数。一共有两种类型的钩子:局部的和远程的。 局部钩子仅钩挂您自己进程的事件。 远程的钩子还可以将钩挂其它进程发生的事件。远程的钩子又有两种: 基于线程的 它将捕获其它进程中某一特定线程的事件。简言之,就是可以用来观察其它进程中的某一特定线程将发生的事件。 系统范围的 将捕捉系统中所有进程将发生的事件消息。 安装钩子函数将会影响系统的性能。监测“系统范围事件”的系统钩子特别明显。因为系统在处理所有的相关事件时都将调用您的钩子函数,这样您的系统将会明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可以预先截获其它进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。记住:功能强大也意味着使用时要负责任。 在正确使用钩子函数前,我们先讲解钩子函数的工作原理。当您创建一个钩子时,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。当一个事件发生时,如果您安装的是一个局部钩子,您进程中的钩子函数将被调用。如果是一个远程钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用远程钩子,就必须把该钩子函数放到动态链接库中去。当然有两个例外:工作日志钩子和工作日志回放钩子。这两个钩子的钩子函数必须在安装钩子的线程中。原因是:这两个钩子是用来监控比较底层的硬件事件的,既然是记录和回放,所有的事件就当然都是有先后次序的。所以如果把回调函数放在DLL中,输入的事件被放在几个线程中记录,所以我们无法保证得到正确的次序。故解决的办法是:把钩子函数放到单个的线程中,譬如安装钩子的线程。 钩子一共有14种,以下是它们被调用的时机: 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_JOURNALRECORD 当WINDOWS从硬件队列中获得消息时 WH_JOURNALPLAYBACK 当一个事件从系统的硬件输入队列中被请求时 WH_SHELL 当关于WINDOWS外壳事件发生时,譬如任务条需要重画它的按钮. WH_CBT 当基于计算机的训练(CBT)事件发生时 WH_FOREGROUNDIDLE 由WINDOWS自己使用,一般的应用程序很少使用 WH_DEBUG 用来给钩子函数除错 现在我们知道了一些基本的理论,现在开始讲解如何安装/卸载一个钩子。 要安装一个钩子,您可以调用SetWindowHookEx函数。该函数的原型如下: SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD HookType 是我们上面列出的值之一,譬如: WH_MOUSE, WH_KEYBOARD pHookProc 是钩子函数的地址。如果使用的是远程的钩子,就必须放在一个DLL中,否则放在本身代码中 hInstance 钩子函数所在DLL的实例句柄。如果是一个局部的钩子,该值为NULL ThreadID 是您安装该钩子函数后想监控的线程的ID号。该参数可以决定该钩子是局部的还是系统范围的。如果该值为NULL,那么该钩子将被解释成系统范围内的,那它就可以监控所有的进程及它们的线程。如果您指定了您自己进程中的某个线程ID 号,那该钩子是一个局部的钩子。如果该线程ID是另一个进程中某个线程的ID,那该钩子是一个全局的远程钩子。这里有两个特殊情况:WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK总是代表局部的系统范围的钩子,之所以说是局部,是因为它们没有必要放到一个DLL中。WH_SYSMSGFILTER 总是一个系统范围内的远程钩子。其实它和WH_MSGFILTER钩子类似,如果把参数ThreadID设成0的话,它们就完全一样了。 如果该函数调用成功的话,将在eax中返回钩子的句柄,否则返回NULL。您必须保存该句柄,因为后面我们还要它来卸载钩子。要卸载一个钩子时调用UnhookWidowHookEx函数,该函数仅有一个参数,就是欲卸载的钩子的句柄。如果调用成功的话,在eax中返回非0值,否则返回NULL。 现在您知道了如何安装和卸载一个钩子了,接下来我们将看看钩子函数。. 只要您安装的钩子的消息事件类型发生,WINDOWS就将调用钩子函数。譬如您安装的钩子是WH_MOUSE类型,那么只要有一个鼠标事件发生时,该钩子函数就会被调用。不管您安装的时那一类型钩子,钩子函数的原型都时是一样的: HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD nCode 指定是否需要处理该消息 wParam 和 lParam 包含该消息的附加消息 HookProc 可以看作是一个函数名的占位符。只要函数的原型一致,您可以给该函数取任何名字。至于以上的几个参数及返回值的具体含义各种类型的钩子都不相同。譬如: WH_CALLWNDPROC nCode 只能是HC_ACTION,它代表有一个消息发送给了一个窗口 wParam 如果非0,代表正被发送的消息 lParam 指向CWPSTRUCT型结构体变量的指针 return value: 未使用,返回0 WH_MOUSE nCode 为HC_ACTION 或 HC_NOREMOVE wParam 包含鼠标的事件消息 lParam 指向MOUSEHOOKSTRUCT型结构体变量的指针 return value: 如果不处理返回0,否则返回非0值 所以您必须查询您的WIN32 API 指南来得到不同类型的钩子的参数的详细定义以及它们返回值的意义。这里还有一个问题需要注意:所有的钩子都串在一个链表上,最近加入的钩子放在链表的头部。当一个事件发生时,WINDOWS将按照从链表头到链表尾调用的顺序。所以您的钩子函数有责任把消息传到下一个链中的钩子函数。当然您可以不这样做,但是您最好明白这时这么做的原因。在大多数的情况下,最好把消息事件传递下去以便其它的钩子都有机会获得处理这一消息的机会。调用下一个钩子函数可以调用函数CallNextHookEx。该函数的原型如下: CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD hHook 时是您自己的钩子函数的句柄。利用该句柄可以遍历钩子链。 nCode, wParam and lParam 您只要把传入的参数简单传给CallNextHookEx即可。 请注意:对于远程钩子,钩子函数必须放到DLL中,它们将从DLL中映射到其它的进程空间中去。当WINDOWS映射DLL到其它的进程空间中去时,不会把数据段也进行映射。简言之,所有的进程仅共享DLL的代码,至于数据段,每一个进程都将有其单独的拷贝。这是一个很容易被忽视的问题。您可能想当然的以为,在DLL中保存的值可以在所有映射该DLL的进程之间共享。在通常情况下,由于每一个映射该DLL的进程都有自己的数据段,所以在大多数的情况下您的程序运行得都不错。但是钩子函数却不是如此。对于钩子函数来说,要求DLL的数据段对所有的进程也必须相同。这样您就必须把数据段设成共享的,这可以通过在链接开关中指定段的属性来实现。在MASM中您可以这么做: /SECTION:<section name>, S已初期化的段名是.data,未初始化的段名是.bss。`加入您想要写一个包含钩子函数的DLL,而且想使它的未初始化的数据段在所有进程间共享,您必须这么做: link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........S 代表该段是共享段。 例子:一共有两个模块:一个是GUI部分,另一个是安装和卸载钩子的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 <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

;----------------------------------------------------- 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

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

26#
0758河台小赵 发表于 2004-9-14 15:59:00
分析:该应用程序的主窗口中包括三个编辑控件,它们将分别显示当前鼠标光标所在位置的窗口类名、窗口句柄和窗口过程的地址。还有两个按钮:“Hook”和“Eixt”。当您按下Hook时,应用程序将钩挂鼠标输入的事件消息,该按钮的文本将变成“Unhook”。当您把鼠标关标滑过一个窗口时,该窗口的有关消息将显示在主窗口中。当您按下“Unhook”时,应用程序将卸载钩子。 主窗口使用一个对话框来作为它的主窗口。它自定义了一个消息WM_MOUSEHOOK,用来在主窗口和DLL之间传递消息。当主窗口接收到该消息时,wParam中包含了光标所在位置的窗口的句柄。当然这是我们做的安排。我这么做只是为了方便。您可以使用您自己的方法在主应用程序和DLL之间进行通讯。

.if HookFlag==FALSE invoke InstallHook,hDlg .if eax!=NULL mov HookFlag,TRUE invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText .endif

该应用程序有一个全局变量,HookFlag,它用来监视钩子的状态。如果安装来钩子它就是TRUE,否则是FALSE。 当用户按下Hook按钮时,应用程序检查钩子是否已经安装。如果还没有的话,它将调用DLL中引出的函数InstallHook来安装它。注意我们把主对话框的句柄传递给了DLL,这样这个钩子DLL就可以把WM_MOUSEHOOK消息传递给正确的窗口了。当应用程序加载时,钩子DLL也同时加载。时机上当主程序一旦加载到内存中后,DLL就立即加载。DLL的入口点函数载主程序的第一条语句执行前就前执行了。所以当主程序执行时,DLL已经初始化好了。我们载入口点处放入如下代码:

.if reason==DLL_PROCESS_ATTACH push hInst pop hInstance .endif

该段代码把DLL自己的实例句柄放到一个全局变量中保存。由于入口点函数是在所有函数调用前被执行的,所以hInstance总是有效的。我们把该变量放到.data中,使得每一个进程都有自己一个该变量的值。因为当鼠标光标停在一个窗口上时,钩子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函数来安装一个鼠标钩子。该函数的返回值放在全局变量hHook中,将来在UnhookWindowsHookEx中还要使用。在调用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函数,它会引起死锁。MOUSEHOOKSTRUCT的定义如下:

MOUSEHOOKSTRUCT STRUCT DWORD pt POINT <> hwnd DWORD ? wHitTestCode DWORD ? dwExtraInfo DWORD ? MOUSEHOOKSTRUCT ENDS pt 是当前鼠标所在的屏幕位置。 hwnd 是将接收鼠标消息的窗口的句柄。通常它是鼠标所在处的窗口,但是如果窗口调用了SetCapture,鼠标的输入将到向到这个窗口。因我们不用该成员变量而是用WindowFromPoint函数。 wHitTestCode 指定hit-test值,该值给出了更多的鼠标位置值。它指定了鼠标在窗口的那个部位。该值的完全列表,请参考WIN32 API 指南中的WM_NCHITTEST消息。 dwExtraInfo 该值包含了相关的信息。一般该值由mouse_event函数设定,可以调用GetMessageExtraInfo来获得。

当主窗口接收到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

为了避免重绘文本时的抖动,我们把已经在编辑空间中线时的文本和我们将要显示的对比。如果相同,就可以忽略掉。得到类名调用GetClassName,得到窗口过程调用GetClassLong并传入GCL_WNDPROC标志,然后把它们格式化成文本串并放到相关的编辑空间中去。

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

当用户按下Unhook后,主程序调用DLL中的UninstallHook函数。该函数调用UnhookWindowsHookEx函数。然后,它把按钮的文本换回“Hook”,HookFlag的值设成FALSE再清除掉编辑控件中的文本。 链接器的开关选项如下:

Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS

它指定.bss段作为一个共享段以便所有映射该DLL的进程共享未初始化的数据段。如果不用该开关,您DLL中的钩子就不能正常工作了。

  1. &lt;SCRIPT language=JavaScript1.1 src="../lion-tut-c13.files/textclick"&gt;
  2. &lt;script&gt;
复制代码
回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

27#
0758河台小赵 发表于 2004-9-14 15:59:00
启动画面 上一章我们学习了位图的使用.在这一章我们要用上帝赋予我们的创造力来融会贯通上一章我们学到的知识.那就是研究如何用位图来创建启动画面. 你可以在这里下载示范: the example. 理论 首先,我们先要搞清楚什么是启动画面.举个简单的例子:我们启动某些作的专业一点的程序时(比如Netscape,Adobe Acrobat等)会先跳出一个启动画面.上面通常有一些版权信息,版本号等.与此同时,程序后台正做着一些程序的加载或初始化工作.这个启动画面有别于一般的窗口.它没有标题栏,没有系统菜单,也没有边框.只有一张位图在屏幕上显示一会儿,然后消失 .在这一章我们来试试自己做一个. 第一步你可能会想到把要显示的位图包含到资源文件中去.但是这样做有一个缺点.你的程序只在启动的时候显示这张位图,可是它却至始至终存在于你的内存中,直到你把程序关掉.这不能不说是对内存的极大浪费.好办法是:创建一个"资源"DLL(动态连接库)来包含位图和它独特的显示代码.这样,你就可以在想显示启动画面的时候加载他,用完了就卸载它.所以,我们的程序需要2个模块:主程序和启动画面DLL.我们要把位图放到这个DLL的资源中去. 基本步骤如下: 把位图作为一个位图资源放到DLL中去. 主程序调用 LoadLibrary 把 dll 加载到内存中去. DLL 被调用后,它会建立一个定时器用于管理启动画面显示的时间.然后,注册并创建一个没有 标题和边框的窗口.同时在窗口的客户区显示位图. 等启动画面的显示时间长度到达你预先的设定值,启动画面消失,控制权回到主程序手中. 主程序调用 FreeLibrary 从内存中卸载 DLL .然后,做它该做的事去. 下面我们来研究细节部分 加载/卸载 DLL你可以用 LoadLibrary 灵活的加载一个 DLL ,它的格式如下: LoadLibrary proto lpDLLName:DWORD它只有一个参数: 你想要加载的 DLL 的名称所在的地址.调用成功返回指向该DLL模块的句柄,反之返回NULL. 要卸载 DLL, 则调用 FreeLibrary: FreeLibrary proto hLib:DWORD它也只有一个参数: 你想要卸载的 DLL 模块的句柄(通常就是上面那个函数返回的啦). 怎样使用定时器首先, 你要用 SetTimer 创建一个定时器: SetTimer proto hWnd:DWORD, TimerID:DWORD, uElapse:DWORD, lpTimerFunc:DWORD

hWnd接受这个定时器消息的窗口的句柄.如果,你的定时器不需要窗口接受它的消息,你也可以 用NULL作为参数 TimerID定时器的 ID 值. 由你自己定义. uElapse 定时器定的时间.以ms(千分之一秒)为单位. lpTimerFunc 处理该定时器消息的函数所在的地址.如果你用NULL作为该参数,那么定时器的消息会被送给 hWnd 参数所指定的窗口.

SetTimer 如果成功则返回定时器的 ID 否则返回 NULL. 所以最好不要把定时器的ID设为0(Pheadnius:NULL代表0, 记得吗?).

你可以用2种方法创建定时器: 如果你有一个窗口并且定时器把消息传给这个窗口.那么你需要把所有的4个参数都传送给 Settimer 函数 (lpTimerFunc参数必须为NULL). 如果你没有窗口或者你不想让窗口处理定时器的消息,那么你必须在窗口句柄中传送一个NULL.同时你要指定用于处理定时器消息的函数的地址. 在这个例子中我们要使用第一种方法. 当你设定的时间到了, 与定时器相连的窗口会收到 WM_TIMER 消息.例如,你指定 uElapse 的值为 1000, 你的窗口每过一秒都会收到 WM_TIMER 消息. 等到你再也不需要这个定时器了,就用 KillTimer 来去除定时器. KillTimer proto hWnd:DWORD, TimerID:DWORD例子:;----------------------------------------------------------------------- ; 主程序 ;----------------------------------------------------------------------- .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.data ClassName db "SplashDemoWinClass",0 AppName db "Splash Screen Example",0 Libname db "splash.dll",0

.data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke LoadLibrary,addr Libname .if eax!=NULL invoke FreeLibrary,eax .endif invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, 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 mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 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,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .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 .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start

;-------------------------------------------------------------------- ; 位图 DLL ;-------------------------------------------------------------------- .386 .model flat, stdcall include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\gdi32.lib .data BitmapName db "MySplashBMP",0 ClassName db "SplashWndClass",0 hBitMap dd 0 TimerID dd 0

.data hInstance dd ?

.code

DllEntry proc hInst:DWORD, reason:DWORD, reserved1:DWORD .if reason==DLL_PROCESS_ATTACH ; When the dll is loaded push hInst pop hInstance call ShowBitMap .endif mov eax,TRUE ret DllEntry Endp ShowBitMap proc LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,0 invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\ WS_POPUP,CW_USEDEFAULT,\ CW_USEDEFAULT,250,250,NULL,NULL,\ hInstance,NULL mov hwnd,eax INVOKE ShowWindow, hwnd,SW_SHOWNORMAL .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 ShowBitMap endp WndProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD LOCAL ps:PAINTSTRUCT LOCAL hdc:HDC LOCAL hMemoryDC:HDC LOCAL hOldBmp:DWORD LOCAL bitmap:BITMAP LOCAL DlgHeight:DWORD LOCAL DlgWidth:DWORD LOCAL DlgRect:RECT LOCAL DesktopRect:RECT

.if uMsg==WM_DESTROY .if hBitMap!=0 invoke DeleteObject,hBitMap .endif invoke PostQuitMessage,NULL .elseif uMsg==WM_CREATE invoke GetWindowRect,hWnd,addr DlgRect invoke GetDesktopWindow mov ecx,eax invoke GetWindowRect,ecx,addr DesktopRect push 0 mov eax,DlgRect.bottom sub eax,DlgRect.top mov DlgHeight,eax push eax mov eax,DlgRect.right sub eax,DlgRect.left mov DlgWidth,eax push eax mov eax,DesktopRect.bottom sub eax,DlgHeight shr eax,1 push eax mov eax,DesktopRect.right sub eax,DlgWidth shr eax,1 push eax push hWnd call MoveWindow invoke LoadBitmap,hInstance,addr BitmapName mov hBitMap,eax invoke SetTimer,hWnd,1,2000,NULL mov TimerID,eax .elseif uMsg==WM_TIMER invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL invoke KillTimer,hWnd,TimerID .elseif uMsg==WM_PAINT invoke BeginPaint,hWnd,addr ps mov hdc,eax invoke CreateCompatibleDC,hdc mov hMemoryDC,eax invoke SelectObject,eax,hBitMap mov hOldBmp,eax invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap invoke StretchBlt,hdc,0,0,250,250,\ hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY invoke SelectObject,hMemoryDC,hOldBmp invoke DeleteDC,hMemoryDC invoke EndPaint,hWnd,addr ps .elseif uMsg==WM_LBUTTONDOWN invoke DestroyWindow,hWnd .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp

End DllEntry 分析:我们首先要再主程序中检验这段代码. invoke LoadLibrary,addr Libname .if eax!=NULL invoke FreeLibrary,eax .endif 我们调用 LoadLibrary 读入名称为 "splash.dll" 的 DLL. 然后, 用 FreeLibrary 卸载. 一直到 DLL 完成初始化, LoadLibrary才会返回. 主程序的任务到此为止. 更有趣的部分再 DLL里.

.if reason==DLL_PROCESS_ATTACH ; When the dll is loaded push hInst pop hInstance call ShowBitMap

DLL 被加载后, Windows 调用它的有 DLL_PROCESS_ATTACH 标记的入口函数. 我们借这个机会显示启动画面. 首先,我们保存 DLL 事例的句柄以供将来使用. 然后, 调用一个叫 ShowBitMap 的函数进行真正的工作. ShowBitMap 注册一个窗口, 创建这个窗口和显示它.就像我们以前创建窗口一样. 有趣的是这个 CreateWindowEx 调用:

INVOKE CreateWindowEx,NULL,ADDR ClassName,NULL,\ WS_POPUP,CW_USEDEFAULT,\ CW_USEDEFAULT,250,250,NULL,NULL,\ hInstance,NULL

注意, 这里的窗口风格仅仅使用了 WS_POPUP . 所以窗口即没有标题栏,也没有边界. 我们同时也限定窗口的宽高为 250x250个像素. 现在窗口创建好了. 在 WM_CREATE 的消息处理代码里我们把这个窗口移到屏幕的中央.代码如下:

invoke GetWindowRect,hWnd,addr DlgRect invoke GetDesktopWindow mov ecx,eax invoke GetWindowRect,ecx,addr DesktopRect push 0 mov eax,DlgRect.bottom sub eax,DlgRect.top mov DlgHeight,eax push eax mov eax,DlgRect.right sub eax,DlgRect.left mov DlgWidth,eax push eax mov eax,DesktopRect.bottom sub eax,DlgHeight shr eax,1 push eax mov eax,DesktopRect.right sub eax,DlgWidth shr eax,1 push eax push hWnd call MoveWindow

它先找到桌面和窗口的大小. 然后,计算出一个窗口左上角的坐标. 使这个窗口能位于屏幕中央.

invoke LoadBitmap,hInstance,addr BitmapName mov hBitMap,eax invoke SetTimer,hWnd,1,2000,NULL mov TimerID,eax

下一步,它用 LoadBitmap 从资源中读入位图并且创建一个定时器.定时器的 ID 为 1 时间间隔为 2 秒. 定时器将每 2 秒 向窗口发送 WM_TIMER 消息.

.elseif uMsg==WM_PAINT invoke BeginPaint,hWnd,addr ps mov hdc,eax invoke CreateCompatibleDC,hdc mov hMemoryDC,eax invoke SelectObject,eax,hBitMap mov hOldBmp,eax invoke GetObject,hBitMap,sizeof BITMAP,addr bitmap invoke StretchBlt,hdc,0,0,250,250,\ hMemoryDC,0,0,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY invoke SelectObject,hMemoryDC,hOldBmp invoke DeleteDC,hMemoryDC invoke EndPaint,hWnd,addr ps

当窗口收到 WM_PAINT 消息, 它创建一个内存DC(Pheadnius:还记得DC吗. 在win32编程中你会经常遇到DC这个词. 它是 Device Context 的缩写, 官方译为"设备描述表". 如果你研究过vc, 你应该对它不陌生. 不过如果你不明白它是什么也不要紧. 你可以把它看作一个句柄. 就是某个设备或某块内存的名称.),然后把位图选进内存DC. 再用 GetObject 函数获得位图的尺寸, 然后用 StretchBlt 把位图显示在窗口上. StretchBlt的作用和 BitBlt 一样,但它可以拉伸或压缩位图到我们希望的大小. 在这里我们希望位图能适合窗口的大小,所以我们 StretchBlt 代替 BitBlt. 之后我们删除内存DC.

.elseif uMsg==WM_LBUTTONDOWN invoke DestroyWindow,hWnd

如果你的程序的使用者每次都要看到启动画面消失才能用, 他们一定会厌烦. 我们可以为用户提供多一种选择. 当他单击启动画面, 它就会消失. 这就是为什么我们要在DLL里处理 WM_LBUTTONDOWN 消息. 收到这个消息后立即就用 DestroyWindow 关掉窗口.

.elseif uMsg==WM_TIMER invoke SendMessage,hWnd,WM_LBUTTONDOWN,NULL,NULL invoke KillTimer,hWnd,TimerID

如果用户选择等待, 那么启动画面会在定时器到了指定的时间后消失. (在本例中, 是 2 秒). 我们可以通过处理 WM_TIMER 消息达到这一目的. 在收到这一消息后,我们可以对窗口传送 WM_LBUTTONDOWN 消息来关掉窗口. 这是为了避免代码重复. 现在, 我们不再需要这个定时器了,所以我们用 KillTimer 删除它. 窗口关闭后,DLL 把控制权还给主程序.

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

28#
0758河台小赵 发表于 2004-9-14 16:00:00
工具提示控件

我们将学习工具提示控件:它是什么如何创建和使用.下载例子

理论:

工具提示是当鼠标在某特定区域上停留时显示的一个矩形窗口.工具提示窗口包含一些编程者想要显示的文本.在这点上,工具提示同状态栏的作用是一样的,所不同的是工具提示当单击或者远离指定区域的时候就会消逝,你可能熟悉与工具栏相关联的工具提示,那些"提示"是工具栏控件提供的便利.如果你想要在其它窗口、控件中显示工具提示的话,就不得不自己创建他们.

既然已经了解了什么是工具提示,就让我们来看看如何创建他们.大致步骤如下:

用CreateWindowEx函数创建工具提示控件. 定义一个工具提示控件将要监视鼠标移动的区域. 将区域传递给工具提示控件 将传递区域的鼠标消息转送给工具提示控件.(这步或许更早,具体依据转播消息的方法) 下面我们就来详细的讨论每一步. 工具提示控件的创建工具提示控件是一种通用控件.同样,要在源代码某处调用InitCommonControls以便MASM能够将你的程序和comctl32.dll连接. 用CreateWindowEx创建工具提示控件,典型代码如下: .data TooltipClassName db "Tooltips_class32",0 .code ..... invoke InitCommonControls invoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL注意窗口风格:TIS_ALWAYSTIP指定了工具提示不管包含指定区域的窗口状态如何,当鼠标移过指定区域的时候,工具提示总是显示.简单的说就是,即使窗口处于非激活状态,鼠标移过工具提示指定区域的时候,工具提示也会出现. 你不必在CreateWindowEx中包括WS_POPUPWS_EX_TOOLWINDOW风格,因为工具提示处理过程会自动加上,你也不必指定工具提示窗口的坐标和宽高,控件会依据要显示的文字自动调节.四个参数,均使用CW_USEDEFAULT ,其余的参数都不太重要. 指定工具

工具提示控件创建了但还没有显示,我们想要当鼠标指针在某个区域之上时显示工具提示窗口.现在需要指定这个区域.我们称这样的区域为"工具",“工具”就是工具提示控件监视鼠标指针是否移过的位于窗口客户区的一个方形区域.如果鼠标指针移过"工具",工具提示窗口就显示."工具"可覆盖整个客户区或者仅仅是它的一部分.因此我们把"工具"分成两种类型,一种是作为一个窗口,另一种则是某窗口客户区的一部分.两种各有所用.覆盖整个客户区的"工具"通常用于按钮、编辑控件等,你不必指定焦点域的坐标和大小:它被假定为窗口的整个客户区.仅覆盖窗口客户区一部分的"工具"在你想把窗口客户区分成几个部分但又不想使用子窗口时特别有用,但需要指定左上角的坐标和宽高.

使用如下的 TOOLINFO 结构定义"工具":

TOOLINFO STRUCT cbSize DWORD ? uFlags DWORD ? hWnd DWORD ? uId DWORD ? rect RECT <> hInst DWORD ? lpszText DWORD ? lParam LPARAM ? TOOLINFO ENDS 域名说明cbSizeTOOLINFO结构的大小.必须填充, 如果这个区域不被正确填充Windows并不会报错,但你会得到不可预料的奇怪结果.uFlags指定焦点域的属性,可以是如下标志的联合: TTF_IDISHWND "ID is hWnd".如果你指定了这个标志,就意味着你要使用覆盖整个客户区的"工具" (上面第一种"工具"). 如果你使用了这个标志,你必须用你要使用的窗口句柄填充uId成员,如果你不指定这个成员,就意味着你要使用第二种"工具"、客户区窗口的一方形区域.在这种情况下,你就必须以方形区域的大小填充rect成员. TTF_CENTERTIP 通常工具提示窗口显示在鼠标的右下方,如果你指定了这个标志,不管鼠标的位置如何,工具提示总显示在焦点域总的中下方. TTF_RTLREADING .如果你的程序不是为阿拉伯或者希伯来语系统设计的,你完全可以不理它,它使得提示文本以从右至左的顺序显示,在其它系统中无效. TTF_SUBCLASS 如果你使用了这个标志,工具提示控件将子类化"工具"所在窗口以便截取发送给它的的鼠标消息,这个标志非常有用,否则你将不得不做更多的工作来向工具提示控件转发消息. hWnd包含"工具"的窗口句柄,如果你指定了TTF_IDISHWND标志,Windows将忽略该值,而使用uId成员的值作为窗口句柄.你需要填充这个域域如果: 你不使用 TTF_IDISHWND标志 (换句话说,你使用局部"工具") 你在 lpszText 成员中指定了LPSTR_TEXTCALLBACK .这个值告诉工具提示控件当需要显示提示窗口时,必须向包含"工具"的窗口查询应该显示什么. 这是一种实时的控件文本更新.如果你需要动态改变提示文本,你应当在 lpszText成员中指定LPSTR_TEXTCALLBACK值,控件就会向hWnd指定的窗口发送TTN_NEEDTEXT 消息. uId这个域的值可能有两种含义,依 uFlags 是否包含TTF_IDISHWND. 如果TTF_IDISHWND标志没有被指定就代表应用程序定义的"工具"ID,由于这意味着你使用仅覆盖客户区一部分的"工具",逻辑的推出一个客户区可能存在多个同样的焦点域(不存在交迭),Hwnd成员的一个窗口句柄就不够了,应用程序定义ID以区分他们因此而显得必要,只要唯一ID可以是任何值. 如果TTF_IDISHWND标志被指定就表示整个客户区都作为焦点域的窗口句柄,你或许会奇怪为什么不用上面提到的hWnd成员的值来储存窗口句柄.答案是:如果lpszText指定为LPSTR_TEXTCALLBACK,Hwnd 可能已经被填充了.还有提供提示文本的窗口和包含"工具"的窗口可能不是同一个!(你可以设计一个提供两种服务的窗口程序,但太严格了,在这点上,微软为我们提供了更大的自由,欢呼吧!) rect指定"工具"大小的rect结构.这个结构定义了一个以hWnd指定窗口客户区左上角为基点的方形大小,简言之,如果你想指定客户区的一部分作为"工具"就得填充这个结构,如果你指定了TTF_IDISHWND标志 ,控件就会忽略这个值.(你已经选择整个客户区作为"工具") hInst如果lpszText指定了字符串资源的标识,包含将作为工具提文本字符串资源的实例句柄.听起来有点费解,阅读一下lpszText的说明就可以明白这个域是干什么用的了.若lpszText不包含字符串资源标识,控件会忽略这个域.lpszText这个域可以有如下几个值: 如果指定为LPSTR_TEXTCALLBACK, 工具提示控件就会向HWnd窗口发送TTN_NEEDTEXT消息以获得将要显示的字符串.提示文本的动态更新方法:每次显示提示窗口都改变提示文本. 如果在这个域中指定字符串资源标识,当控件要在提示窗口中显示提示文本时,就搜索hInst成员标识的实例的字符串资源列表.由于字符串资源列表标识总是16位值,这个域的高字节将永远为0,这种方法在你想移植程序时非常有用,由于字符串资源以脚本形式定义,你不必修改源代码.只需要修改字符串列表提示文本就会相应改变,而不必担心引进bugs. 如果这个域的值不是LPSTR_TEXTCALLBACK并且高字节不为零, 控件截取这个值作为提示文本的指针,这是最简单的方法但也最不稳定.通过检查高字节区分字符串资源标识.

总言之,你需要将TOOLINFO结构传递给工具提示控件之前填充填充好,它描述了你期望的"工具"属性. 向工具提示控件注册"工具"填充完TOOLINFO结构后, 必须将其传递给控件 . 一个工具提示控件可以控制很多"工具",因此不必为一个窗口创建很多控件,为了注册"工具",向控件发送TTM_ADDTOOL消息 wParam不使用,lParam必须包含要注册的TOOLINFO结构的指针 .data? ti TOOLINFO <> ....... .code ....... <fill the TOOLINFO structure> ....... invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti成功返回 TRUE,否则返回 FALSE. 发送 TTM_DELTOOL消息取消注册. 向工具提示控件转发鼠标消息以上步骤完毕之后,控件知道了应当监视那一块区域和应该在提示窗口显示什么.唯一缺乏的就是激发机制. 想想看:"工具"指定的其它窗口的客户区的区域.控件如何截取发送向该窗口的消息呢?实际中需要截取消息以便了解鼠标停留了多长时间,当指定时间流逝以后,控件显示提示窗口.有两种方法: 一种需要包含"工具"窗口的合作,另一种则不需要. 包含"工具"的窗口必须向控件发送 TTM_RELAYEVENT 以转发消息. lParam是指向要转发消息MSG的指针 控件仅处理如下鼠标消息 : WM_LBUTTONDOWN WM_MOUSEMOVE WM_LBUTTONUP WM_RBUTTONDOWN WM_MBUTTONDOWN WM_RBUTTONUP WM_MBUTTONUP 其它消息全被忽略,因此包含"工具"的窗口的处理过程必须包含像这样的选择:

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD ....... if uMsg==WM_CREATE ............. elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || uMsg==WM_LBUTTONUP || uMsg==WM_RBUTTONDOWN || uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP || uMsg==WM_MBUTTONUP invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg ..........

.你可以在TOOLINFO结构的uFlags成员指定 TTF_SUBCLASS标志。此标志告诉控件子类化包含"工具"的窗口以便无需窗口的协作便可捕获鼠标消息。由于除了控件自己处理截获的鼠标消息和指定TTF_SUBCLASS标志之外不用编写多余的代码,因此很易于使用。 就是这些了,到这步为止,控件已经全功能了.还有几个你应当知道的相关消息. TTM_ACTIVATE.如果你想动态的允许或者禁止工具提示控件,这个小消息就是为你而备.wParam值为TRUE,允许控件.若为FALSE,禁止控件.控件初始创建的时候无需发送消息激活他,便被自动设为允许状态. TTM_GETTOOLINFO and TTM_SETTOOLINFO. 如果你想在把TOOLINFO结构传递给控件之后获得或者改变其值,使用此消息.你需要用正确的uId and hWnd值指定要改变的"工具".如果你只想改变rect成员的值,使用TTM_NEWTOOLRECT 消息,如果仅想改变提示文本,使用TTM_UPDATETIPTEXT消息. TTM_SETDELAYTIME. 使用此消息指定控件显示提示文本时的时间延迟. 例子:例子是一个有两个按钮的对话框,对话框的客户区分为4部分:左上、右上、左下、右下.每个区域都指定为有自己提示文本的"工具",两个按钮也有自己的提示文本.

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include \masm32\include\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD EnumChild proto :DWORD,:DWORD SetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD .const IDD_MAINDIALOG equ 101 .data ToolTipsClassName db "Tooltips_class32",0 MainDialogText1 db "This is the upper left area of the dialog",0 MainDialogText2 db "This is the upper right area of the dialog",0 MainDialogText3 db "This is the lower left area of the dialog",0 MainDialogText4 db "This is the lower right area of the dialog",0 .data? hwndTool dd ? hInstance dd ? .code start: invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULL invoke ExitProcess,eax

DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD LOCAL ti:TOOLINFO LOCAL id:DWORD LOCAL rect:RECT .if uMsg==WM_INITDIALOG invoke InitCommonControls invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\ TTS_ALWAYSTIP,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInstance,NULL mov hwndTool,eax mov id,0 mov ti.cbSize,sizeof TOOLINFO mov ti.uFlags,TTF_SUBCLASS push hDlg pop ti.hWnd invoke GetWindowRect,hDlg,addr rect invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect inc id invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rect inc id invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rect inc id invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rect invoke EnumChildWindows,hDlg,addr EnumChild,addr ti

.elseif uMsg==WM_CLOSE invoke EndDialog,hDlg,NULL .else mov eax,FALSE ret .endif mov eax,TRUE ret DlgProc endp

EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD LOCAL buffer[256]:BYTE mov edi,lParam assume edi:ptr TOOLINFO push hwndChild pop [edi].uId or [edi].uFlags,TTF_IDISHWND invoke GetWindowText,hwndChild,addr buffer,255 lea eax,buffer mov [edi].lpszText,eax invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi assume edi:nothing ret EnumChild endp

SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD mov edi,lpti mov esi,lprect assume esi:ptr RECT assume edi:ptr TOOLINFO .if id==0 mov [edi].rect.left,0 mov [edi].rect.top,0 mov eax,[esi].right sub eax,[esi].left shr eax,1 mov [edi].rect.right,eax mov eax,[esi].bottom sub eax,[esi].top shr eax,1 mov [edi].rect.bottom,eax .elseif id==1 mov eax,[esi].right sub eax,[esi].left shr eax,1 inc eax mov [edi].rect.left,eax mov [edi].rect.top,0 mov eax,[esi].right sub eax,[esi].left mov [edi].rect.right,eax mov eax,[esi].bottom sub eax,[esi].top mov [edi].rect.bottom,eax .elseif id==2 mov [edi].rect.left,0 mov eax,[esi].bottom sub eax,[esi].top shr eax,1 inc eax mov [edi].rect.top,eax mov eax,[esi].right sub eax,[esi].left shr eax,1 mov [edi].rect.right,eax mov eax,[esi].bottom sub eax,[esi].top mov [edi].rect.bottom,eax .else mov eax,[esi].right sub eax,[esi].left shr eax,1 inc eax mov [edi].rect.left,eax mov eax,[esi].bottom sub eax,[esi].top shr eax,1 inc eax mov [edi].rect.top,eax mov eax,[esi].right sub eax,[esi].left mov [edi].rect.right,eax mov eax,[esi].bottom sub eax,[esi].top mov [edi].rect.bottom,eax .endif push lpText pop [edi].lpszText invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti assume edi:nothing assume esi:nothing ret SetDlgToolArea endp

end start

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

29#
0758河台小赵 发表于 2004-9-14 16:01:00

分析:

创建主对话框窗口之后,使用CreateWindowEx创建工具提示控件.

invoke InitCommonControls invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\ TTS_ALWAYSTIP,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInstance,NULL mov hwndTool,eax

之后,我们继续定义对话框四个角作为焦点域.

mov id,0 ; 焦点域ID mov ti.cbSize,sizeof TOOLINFO mov ti.uFlags,TTF_SUBCLASS ; 告诉控件子类化窗口. push hDlg pop ti.hWnd ; 包含焦点域的窗口句柄 invoke GetWindowRect,hDlg,addr rect ; 获得客户区的大小 invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect

我们初始化TOOLINFO结构. 注意我们要把客户区分成4个焦点域,因此我们需要知道客户区的大小,所以调用GetWindowRect.因为我们不想自己向控件转发消息,因此指定TIF_SUBCLASS 标志. SetDlgToolArea 是计算焦点域矩形范围的并向控件注册的函数,我不详细解释计算过程.只说明它把对话框分成4个焦点域.然后向控件发送TTM_ADDTOOL 消息, 在lParam参数中传递TOOLINFO结构的地址.

invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

在四个控件注册之后,我们来看看对话框的按钮,我们可以用ID来处理每个按钮,但是实在太乏味了.我们使用EnumChildWindows函数列举对话框上的所有控件并把他们注册给控件,EnumChildWindows原型如下: EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD

hWnd 是父窗口句柄.

lpEnumFunc 是每个控件将调用的EnumChildProc函数地址.lParam 是应用程序定义的要传给EnumChildProc 函数的值. EnumChildProc 函数定义如下:

EnumChildProc proto hwndChild:DWORD, lParam:DWORDhwndChild是EnumChildWindows函数枚举的句柄. lParam 就是你传递给EnumChildWindows函数的同一个lParam. 在例子中.我们如此调用 EnumChildWindows 函数: invoke EnumChildWindows,hDlg,addr EnumChild,addr ti 我们把TOOLINFO结构的地址放在lParam参数中传递,是因为我们要在EnumChild函数中注册每个子控件.如果我们不使用这种方法,就需要将ti声明为全局变量,但这可能会引入很多bug. 当我们调用 EnumChildWindows时, Windows会枚举出对话框上所有的子控件并为每个子控件调用一次EnumChild f函数. 这样如果我们的对话框有两个控件,EnumChild将被调用两次. EnumChild 函数填充TOOLINFO 结构的相应成员并向控件注册. EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD LOCAL buffer[256]:BYTE mov edi,lParam assume edi:ptr TOOLINFO push hwndChild pop [edi].uId ; we use the whole client area of the control as the tool or [edi].uFlags,TTF_IDISHWND invoke GetWindowText,hwndChild,addr buffer,255 lea eax,buffer ; use the window text as the tooltip text mov [edi].lpszText,eax invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi assume edi:nothing ret EnumChild endp 注意在例子中,我们使用了一种不同"工具":覆盖整个客户区的"工具",因此我们需要用包含"工具"窗口的句柄来填充uID成员,也必须在uFlags 成员中指定TTF_IDISHWND标志.
回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

30#
0758河台小赵 发表于 2004-9-14 16:01:00
Win32调试API 第一部分

在本教程中,我们将学习Win32提供给开发者的用于调试的原语. 在教程的结尾,我们将学习如何调试一个进程. 下载 例子程序.

理论:

Win32有一些供程序员使用的API,它们提供相当于调试器的功能. 他们被称作Win32调试API(或原语).利用这些API,我们可以:

加载一个程序或捆绑到一个正在运行的程序上以供调试 获得被调试的程序的低层信息,例如进程ID,进入地址,映像基址等. 当发生与调试有关的事件时被通知,例如进程/线程的开始/结束, DLL的加载/释放等. 修改被调试的进程或线程

简而言之,我们可以用这些API写一个简单的调试器.由于这个题目有些过大,我把它分为几部分,而本教程就是它的第一部分.在本教程中,我将讲解一些基本概念及Win32调试API的大致框架. 使用Win32调试API的步骤如下:

创建一个进程或捆绑到一个运行中的进程上. 这是使用Win32调试API的第一步.由于我们的程序要扮演调试器的角色,我们要找一个供调试的程序.一个被调试的程序被称为debuggee.可以通过以下两种方式获得debuggee: 通过CreateProcess创建debuggee进程.为了创建被调试的进程,必须指定DEBUG_PROCESS标志.这一标志告诉Windows我们要调试该进程. 当debuggee中发生重要的与调试有关的事件(调试事件)时,Windows 会向我们的程序发送通知.debuggee会立即挂起以等待我们的程序准备好.如果debuggee还创建了子进程,Windows还会为每个子进程中的调试事件向我们的程序发送通知.这一特性通常是不必要的.我们可以通过指定DEBUG_ONLY_THIS_PROCESSDEBUG_PROCESS的组合标志来禁止它. 我们也可以用 DebugActiveProcess标志捆绑到一个运行中的进程上. 等待调试事件. 在获得了一个debuggee进程后,debuggee的主线程被挂起,这种状况将持续到我们的程序调用WaitForDebugEvent为止.这个函数和其他的WaitForXXX函数相似,比如说,它阻塞调用线程直到等待的事件发生.对这个函数来说, 它等待由Windows发送的调试事件.下面是它的定义:

WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

lpDebugEvent is the address of a DEBUG_EVENT这个结构将被填入关于debuggee中发生的调试事件的信息.

dwMilliseconds 该函数等待调试事件的时间,以毫秒为单位.如果这段时间没有调试事件发生, WaitForDebugEvent返回调用者.另一方面,如果将该参数指定为 INFINITE 常数,函数将一直等待直到调试事件发生.

现在我们看一下DEBUG_EVENT 结构.

DEBUG_EVENT STRUCT dwDebugEventCode dd ? dwProcessId dd ? dwThreadId dd ? u DEBUGSTRUCT <> DEBUG_EVENT ENDS

dwDebugEventCode 该值指定了等待发生的调试事件的类型.因为有很多种类型的事件发生,我们的程序要检查该值,知道要发生事件的类型并做出响应. 该值可能的取值如下:

取值含义CREATE_PROCESS_DEBUG_EVENT进程被创建.当debuggee进程刚被创建(还未运行) 或我们的程序刚以DebugActiveProcess被捆绑到一个运行中的进程时事件发生. 这是我们的程序应该获得的第一个事件.EXIT_PROCESS_DEBUG_EVENT进程退出.CREATE_THEAD_DEBUG_EVENT当一个新线程在deuggee进程中创建或我们的程序首次捆绑到运行中的进程时事件发生.要注意的是当debugge的主线程被创建时不会收到该通知. EXIT_THREAD_DEBUG_EVENTdebuggee中的线程退出时事件发生.debugee的主线程退出时不会收到该通知.我们可以认为debuggee的主线程与debugge进程是同义词. 因此, 当我们的程序看到CREATE_PROCESS_DEBUG_EVENT标志时,对主线程来说,就是CREATE_THREAD_DEBUG_EVENT标志.LOAD_DLL_DEBUG_EVENTdebuggee装入一个DLL.当PE装载器第一次分解指向DLL的链接时,我们将收到这一事件. (当调用CreateProcess装入 debuggee时)并且当debuggee调用LoadLibrary时也会发生.UNLOAD_DLL_DEBUG_EVENT一个DLL从debuggee中卸载时事件发生. EXCEPTION_DEBUG_EVENT在debuggee中发生异常时事件发生. 注意: 该事件仅在debuggee开始它的第一条指令之前发生一次.异常实际上是一个调试中断(int 3h).如果想恢复debuggee事,以 DBG_CONTINUE 标志调用ContinueDebugEvent 函数. 不要使用DBG_EXCEPTION_NOT_HANDLED 标志否则debuggee会在NT下拒绝运行(Win98下运行得很好).OUTPUT_DEBUG_STRING_EVENT当debuggee调用DebugOutputString函数向我们的程序发送消息字符串时该事件发生. RIP_EVENT系统调试发生错误

dwProcessIddwThreadId发生调试事件的进程和线程Id.我们可以用这些值作为我们感兴趣的进程或线程的标志符.记住如果我们使用CreateProcess来装载debuggee,我们仍可在PROCESS_INFO结构中获得debuggee的进程和线程.我们可以用这些值来区别调试事件是发生在debuggee中还是它的子进程中(当没有指定 DEBUG_ONLY_THIS_PROCESS 标志时).

u 是一个联合,包含了调试事件的更多信息.根据上面dwDebugEventCode的不同,它可以是以下结构:

dwDebugEventCodeu的解释CREATE_PROCESS_DEBUG_EVENT名为CreateProcessInfoCREATE_PROCESS_DEBUG_INFO结构EXIT_PROCESS_DEBUG_EVENT名为ExitProcessEXIT_PROCESS_DEBUG_INFO结构CREATE_THREAD_DEBUG_EVENT名为CreateThreadCREATE_THREAD_DEBUG_INFO结构EXIT_THREAD_DEBUG_EVENT名为ExitThreadEXIT_THREAD_DEBUG_EVENT 结构LOAD_DLL_DEBUG_EVENT名为LoadDllLOAD_DLL_DEBUG_INFO 结构UNLOAD_DLL_DEBUG_EVENT名为UnloadDllUNLOAD_DLL_DEBUG_INFO结构EXCEPTION_DEBUG_EVENT名为ExceptionEXCEPTION_DEBUG_INFO结构OUTPUT_DEBUG_STRING_EVENT名为DebugStringOUTPUT_DEBUG_STRING_INFO 结构RIP_EVENT名为RipInfoRIP_INFO 结构

我不会在这一个教程里讲所有这些结构的细节,这里只详细讲一下CREATE_PROCESS_DEBUG_INFO 结构. 假设我们的程序调用了WaitForDebugEvent函数并返回,我们要做的第一件事就是检查dwDebugEventCode中的值来看debuggee进程中发生了那种类型的调试事件.比如说,如果dwDebugEventCode的值为 CREATE_PROCESS_DEBUG_EVENT,就可认为u的成员为CreateProcessInfo 并用u.CreateProcessInfo来访问.

在我们的程序中做对调试事件的响应. 当WaitForDebugEvent 返回时,这意味着在debuggee进程中发生了调试事件或者发生了超时.所以我们的程序要检查dwDebugEventCode 来作出适当的反应.这里有些象处理Windows消息:由用户来选择和忽略消息. 继续运行debuggee. 当调试事件发生时, Windows挂起了debuggee,所以当我们处理完调试事件,还要让debuggee继续运行.调用ContinueDebugEvent 函数来完成这一过程.

ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

该函数恢复由于调试事件而挂起的线程. dwProcessIddwThreadId是要恢复的线程的进程ID和线程ID,通常这两个值从 DEBUG_EVENT结构的dwProcessIddwThreadId成员获得. dwContinueStatus显示了如何继续报告调试事件的线程.可能的取值有两个: DBG_CONTINUEDBG_EXCEPTION_NOT_HANDLED. 对大多数调试事件,这两个值都一样:恢复线程.唯一的例外是EXCEPTION_DEBUG_EVENT,如果线程报告发生了一个异常调试事件,这意味着在debuggee的线程中发生了一个异常.如果指定了DBG_CONTINUE,线程将忽略它自己的异常处理部分并继续执行.在这种情况下,我们的程序必须在以DBG_CONTINUE恢复线程之前检查并处理异常,否则异常将生生不息地不断发生....如果我们指定了 DBG_EXCEPTION_NOT_HANDLED值,就是告诉Windows我们的程序并不处理异常:Windows将使用debuggee的默认异常处理函数来处理异常. 总而言之,如果我们的程序没有考虑异常,而调试事件又指向debuggee进程中的一个异常的话,就应调用含DBG_CONTINUE标志的ContinueDebugEvent函数.否则,我们的程序就必须以DBG_EXCEPTION_NOT_HANDLED调用 ContinueDebugEvent.但在下面这种情况下必须使用DBG_CONTINUE标志:第一个在ExceptionCode成员中有值EXCEPTION_BREAKPOINTEXCEPTION_DEBUG_EVENT事件.当debuggee开始执行它的第一条指令时,我们的函数将接受到异常调试事件.它事实上是一个调试中断(int 3h).如果我们以DBG_EXCEPTION_NOT_HANDLED调用ContinueDebugEvent 来响应调试事件, Windows NT会拒绝执行debuggee(因为它没有异常处理).所以在这种情况下,要用DBG_CONTINUE标志告诉Windows我们希望该线程继续执行.

继续上面的步骤循环直到debuggee进程退出. 我们的程序必须在一个很象消息循环的无限循环中直到debuggee结束.该循环大体如下:

.while TRUE invoke WaitForDebugEvent, addr DebugEvent, INFINITE .break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT <调试事件处理> invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw

就是说,当开始调试程序时,我们的程序不能和debuggee分开直到它结束.

我们再来总结一下这些步骤:

创建一个进程或捆绑我们的程序到运行中的进程上. 等待调试事件 响应调试事件. 继续执行debuggee. 继续这一无尽循环直到debuggee进程结束 例子:

这个例子调试一个win32程序并显示诸如进程句柄,进程Id,映象基址等.

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "Win32 Debug Example no.1",0 ofn OPENFILENAME <> FilterString db "Executable Files",0,"*.exe",0 db "All Files",0,"*.*",0,0 ExitProc db "The debuggee exits",0 NewThread db "A new thread is created",0 EndThread db "A thread is destroyed",0 ProcessInfo db "File Handle: %lx ",0dh,0Ah db "Process Handle: %lx",0Dh,0Ah db "Thread Handle: %lx",0Dh,0Ah db "Image Base: %lx",0Dh,0Ah db "Start Address: %lx",0 .data? buffer db 512 dup(?) startinfo STARTUPINFO <> pi PROCESS_INFORMATION <> DBEvent DEBUG_EVENT <> .code start: mov ofn.lStructSize,sizeof ofn mov ofn.lpstrFilter, offset FilterString mov ofn.lpstrFile, offset buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION .break .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .endif .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread .endif invoke ExitProcess, 0 end start

分析:

程序首先填充OPENFILENAME结构,调用GetOpenFileName让用户选择要调试的程序.

invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

当接收用户选择后,调用CreateProcess装载程序.并调用GetStartupInfo以默认值填充STARTUPINFO结构.注意我们将DEBUG_PROCESS标志与DEBUG_ONLY_THIS_PROCESS标志组合来仅调试这个程序,不包括子进程.

.while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE

在debuggee被装入后,我们调用WaitForDebugEvent进入无尽的调试循环,WaitForDebugEvent在debuggee中发生调试事件时返回,因为我们指定了INFINITE作为第二个参数.当调试事件发生时, WaitForDebugEvent 返回并填充DBEvent结构.

.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION .break

我们要先检查dwDebugEventCode的值, 如果是EXIT_PROCESS_DEBUG_EVENT,用一个消息框显示"The debuggee exits" 并退出调试循环.

.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION

如果dwDebugEventCode 的值为CREATE_PROCESS_DEBUG_EVENT,我们就在消息框中显示一些感兴趣的底层信息.这些信息从u.CreateProcessInfo获得. CreateProcessInfo是一个CREATE_PROCESS_DEBUG_INFO类型的结构体.你可以查阅Win32 API获得它的更多信息e.

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .endif

如果dwDebugEventCode 的值为EXCEPTION_DEBUG_EVENT,我们就要更进一步检查异常类型.它是一大堆的结构嵌套,但我们可以从ExceptionCode成员获得异常类型.如果ExceptionCode的值为 EXCEPTION_BREAKPOINT并且是第一次发生(或者我们已知道deuggee中没有int 3h指令),我们可以安全地假定在debuggee要执行第一条指令时发生这一异常.在我们完成这些处理后,就可以用 DBG_CONTINUE调用ContinueDebugEvent来继续执行debuggee.接着我们继续等待下一个调试事件的发生.

.elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION .endif

如果dwDebugEventCode 的值为CREATE_THREAD_DEBUG_EVENTEXIT_THREAD_DEBUG_EVENT, 我们的程序显示一个消息框.

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw

除了上面讨论过的 EXCEPTION_DEBUG_EVENT,用DBG_EXCEPTION_NOT_HANDLED标志调用ContinueDebugEvent函数恢复debuggee的执行.

invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread

当debuggee结束时,我们就跳出了调试循环,这时要关闭 debuggee的线程和进程句柄.关闭这些句柄并不意味着要关闭这些进程和线程.只是说不再用这些句柄罢了.

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

31#
0758河台小赵 发表于 2004-9-14 16:02:00
Win32调试API 第二部分我们继续Win32调试API的话题。在本章中,我们将要学习如何修改被调试程序。 下载 the example 理论:

在前面一章中,我们学会了如何装载被调试的进程以及如何处理进程中发生的事件。为了有实际用途,我们的程序应具有修改被调试程序的能力。有好几个API函数用于这一目的。

ReadProcessMemory该函数允许你去读指定的进程的内存。函数原型如下:

ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

hProcess 待读进程的句柄. lpBaseAddress 目标进程中待读内存起始地址。例如,如果你想要读目标 进程中从地址401000h开始的4个字节,该参数值应置为401000h。 lpBuffer 接收缓冲区地址 nSize 想要读的字节数。 lpNumberOfBytesRead 记录实际读取的字节数的变量地址。如果对这个值 不关心,填入NULL即可。

WriteProcessMemory 是对应于ReadProcessMemory的函数,通过它 可以写目标进程的内存。其参数和ReadProcessMemory 相同。

理解接下去的两个函数需要一些进程上下文的有关背景知识。在象Windows这样的 多任务操作系统中,同一时间里可能运行着几个程序。Windows分配给每个线程一个 时间片,当时间片结束后,Windows将冻结当前线程并切换到下一具有最高优先级的 线程。在切换之前,Windows将保存当前进程的寄存器的 内容,这样当在该线程再 次恢复运行时,Windows可以恢复最近一次线程运行的*环境*。保存的寄存器内容总 称为进程上下文。 现在回到我们的主题。当一个调试事件发生时,Windows暂停被调试进程,并保存其 进程上下文。由于进程被暂停运行,我们可以确信其进程上下文内容将保持不变。 可以用GetThreadContext来获取进程上下文内容,并且也可以用GetThreadContext 来修改进程上下文内容。 这两个函数威力非凡。有了他们,对被调试进程你就具有象VxD的能力: 如改变其寄 存器内容,而在被调试程序恢复运行前,这些值将会写回寄存器中。在进程上下文中 所做的任何改动,将都会反映到被调试程序中。想象一下: 甚至可以改变eip寄存器 的内容,这样你可以让程序运行到你想要的任何地方! 在正常情况下是不可能做到这 一点的。

GetThreadContext proto hThread:DWORD, lpContext:DWORD

hThread 你想要获得上下文的线程句柄 lpContext 函数成功返回时用来保存上下文内容的结构指针。

SetThreadContext 参数相同。让我们来看看上下文的结构:

CONTEXT STRUCT ContextFlags dd ? ;---------------------------------------------------------------------------------------------------------- ;当ContextFlags包含CONTEXT_DEBUG_REGISTERS,返回本部分 ;----------------------------------------------------------------------------------------------------------- iDr0 dd ? iDr1 dd ? iDr2 dd ? iDr3 dd ? iDr6 dd ? iDr7 dd ? ;---------------------------------------------------------------------------------------------------------- ;当ContextFlags包含CONTEXT_FLOATING_POINT,返回本部分 ;----------------------------------------------------------------------------------------------------------- FloatSave FLOATING_SAVE_AREA <> ;---------------------------------------------------------------------------------------------------------- ;当ContextFlags包含CONTEXT_SEGMENTS,返回本部分 ;----------------------------------------------------------------------------------------------------------- regGs dd ? regFs dd ? regEs dd ? regDs dd ? ;---------------------------------------------------------------------------------------------------------- ;当ContextFlags包含CONTEXT_INTEGER,返回本部分 ;----------------------------------------------------------------------------------------------------------- regEdi dd ? regEsi dd ? regEbx dd ? regEdx dd ? regEcx dd ? regEax dd ? ;---------------------------------------------------------------------------------------------------------- ;当ContextFlags包含CONTEXT_CONTROL,返回本部分 ;----------------------------------------------------------------------------------------------------------- regEbp dd ? regEip dd ? regCs dd ? regFlag dd ? regEsp dd ? regSs dd ? ;---------------------------------------------------------------------------------------------------------- ;当ContextFlags包含CONTEXT_EXTENDED_REGISTERS,返回本部分 ;----------------------------------------------------------------------------------------------------------- ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS

可以看出,该结构中的成员是对实际处理器的寄存器的模仿。在使用该结构之前 要在ContextFlags 中指定哪些寄存器组用来读写。如要访问所有的寄存器, 你可以置ContextFlagsCONTEXT_FULL 。或者只访问regEbp, regEip, regCs, regFlag, regEsp 或 regSs, 应置ContextFlagsCONTEXT_CONTROL

在使用结构CONTEXT 时还应记住: 它必须是双字对齐的,否则在NT下将得 到奇怪的结果。可以在定义前加上"align dword"。例如:

align dword MyContext CONTEXT <>

例:

第一个例子演示DebugActiveProcess的使用。首先,需要在Windows显示在屏幕上以前运行一个待调试程序win.exe,该程序将处于无限循环运行状态中。然后你运行例子程序,它将把自己与win.exe连接起来,并且修改win.exe的代码,这样win.exe将退出无限循环状态而显示自己的窗口。

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "Win32 Debug Example no.2",0 ClassName db "SimpleWinClass",0 SearchFail db "Cannot find the target process",0 TargetPatched db "Target patched!",0 buffer dw 9090h .data? DBEvent DEBUG_EVENT <> ProcessId dd ? ThreadId dd ? align dword context CONTEXT <> .code start: invoke FindWindow, addr ClassName, NULL .if eax!=NULL invoke GetWindowThreadProcessId, eax, addr ProcessId mov ThreadId, eax invoke DebugActiveProcess, ProcessId .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw .else invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR .endif invoke ExitProcess, 0 end start

;-------------------------------------------------------------------- ; The partial source code of win.asm, our debuggee. It's actually ; the simple window example in tutorial 2 with an infinite loop inserted ; just before it enters the message loop. ;----------------------------------------------------------------------

...... mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax jmp $ <---- Here's our infinite loop. It assembles to EB FE invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .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

分析:

invoke FindWindow, addr ClassName, NULL

我们的程序需要用DebugActiveProcess将自己绑定到被调试程序,这需要知道被调试程序的进程Id。用GetWindowThreadProcessId 可以得到该Id,该函数需要窗口句柄作为参数,因此首先需要知道窗口句柄。 用FindWindow, 我们先指定窗口类的名称,返回的是该类创建的窗口句柄。如 果返回NULL,则表明当前没有该类的窗口。

.if eax!=NULL invoke GetWindowThreadProcessId, eax, addr ProcessId mov ThreadId, eax invoke DebugActiveProcess, ProcessId

得到进程Id后,我们调用DebugActiveProcess。这样就进入等待调试事件的循环中。

.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context

当得到 CREATE_PROCESS_DEBUG_INFO, 这意味着被调试进程已经被暂停运行了。 我们就可以对该进程动手术了。本例中,我们将用NOPs ( 90h 90h)覆盖被调试进程中的无 限循环指令(0EBh 0FEh) 。 首先,需要得到该指令的地址。由于在我们的程序绑定到被调试程序时,被调试程序已经 处于循环语句中了,eip总是指向该指令。我们所要做的是得到eip的值。我们将使用 GetThreadContext来达到此目的。将上下文结构成员中ContextFlags设置 为CONTEXT_CONTROL ,这样告诉GetThreadContext我们需要它去填充上下 文结构的成员中的"控制"寄存器。

invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

得到eip的值以后,可以调用WriteProcessMemory来用NOPs覆盖"jmp $" 指令,这样将使被调试程序退出无限循环。在向用户显示了信息之后,调用ContinueDebugEvent 来恢复被调试程序的运行。由于指令"jmp $"已被Nops覆盖,被调试程序将继续 显示窗口,并进入消息循环。证据是我们在屏幕上观察到了次窗口。

另一个例子与此稍有不同,它是将被调试程序从无限循环中中断。

....... ....... .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context add context.regEip,2 invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION ....... .......

这里仍调用GetThreadContext来获取eip值,但没有去覆盖"jmp $" 指令,而是将 regEip加2,从而"跳过"该指令。结果是当被调试程序 重新获得控制权时,将恢复执行在"jmp $"后的指令。

现在你可以体会到Get/SetThreadContext的威力了。你也可以修改其他寄存器映象,这些值将直接反映到被调试程序中。甚至你可以把int 3h指令插入到被调试进程中。产生断点。

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

32#
0758河台小赵 发表于 2004-9-14 16:02:00
Win32调试API 第三部分在本章中,我们将继续探讨win32调试api。特别地,我们将学习如何去跟踪被调试程序. 下载 例子. 理论:

如果你以前使用过调试器,那么你应对跟踪比较熟悉。当"跟踪"一个程序时,程序在每执行一条指令后将会停止,这使你有机会去检查寄存器/内存中的值。这种单步运行的官方定义为跟踪(tracing)。 单步运行的特色是由CPU本身提供的。标志寄存器的第8位称为陷阱标志trap flag。如果该位设置,则CPU运行于单步模式。CPU将在每条指令后产生一个debug异常。当debug 异常产生后,陷阱标志自动清除。利用win32调试api,我们也可以单步运行被调试程序。方法如下:

调用GetThreadContext, 指定 ContextFlagsCONTEXT_CONTROL, 来获得标志寄存器的值 设置CONTEXT结构成员标志寄存器regFlag中的陷阱标志位 调用 SetThreadContext 等待调式事件。被调试程序将按单步模式执行,在每执行一条指令后,我们将得到调试 事件,u.Exception.pExceptionRecord.ExceptionCode值为EXCEPTION_SINGLE_STEP 如果要跟踪下一条指令,需要再次设置陷阱标志位。 例:

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "Win32 Debug Example no.4",0 ofn OPENFILENAME <> FilterString db "Executable Files",0,"*.exe",0 db "All Files",0,"*.*",0,0 ExitProc db "The debuggee exits",0Dh,0Ah db "Total Instructions executed : %lu",0 TotalInstruction dd 0 .data? buffer db 512 dup(?) startinfo STARTUPINFO <> pi PROCESS_INFORMATION <> DBEvent DEBUG_EVENT <> context CONTEXT <> .code start: mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .break .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP inc TotalInstruction invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw .endif invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread invoke ExitProcess, 0 end start

分析:

该程序先显示一个打开文件对话框,当用户选择了一个可执行文件,它将单步执行该程序,并记录执行的指令数,直到被调试程序退出运行。

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

利用该机会来设置被调试程序为单步运行模式。记住,在执行被调试程序的第一条指令前 windows将发送一个EXCEPTION_BREAKPOINT消息。

mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context

调用GetThreadContext,以被调试程序的当前寄存器内容来填充CONTEXT 结构 特别地,我们需要标志寄存器的当前值。

or context.regFlag,100h

设置标志寄存器映象的陷阱位(第8位)

invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue

然后调用SetThreadContext去覆盖CONTEXT的值。再以DBG_CONTINUE调用 ContinueDebugEvent 来恢复被调试程序的运行。

.elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP inc TotalInstruction

当调试程序中一条指令执行后,我们将接收到EXCEPTION_DEBUG_EVENT的调试事件, 必须要检查u.Exception.pExceptionRecord.ExceptionCode的值。如果该值为 EXCEPTION_SINGLE_STEP,那么,该调试事件是单步运行模式造成的。在这种情况 下,TotalInstruction加一,因为我们确切地知道此时被调试程序执行了一条指令。

invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue

由于陷阱标志在debug异常后自动清除了,如果我们需要继续保持单步运行模式,则必须设置陷阱标志位。 警告: 不要用本教程中的此例子来调试大程序: 跟踪是很慢的。你或许需要等待10 多分钟才能关闭被调试程序。

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

33#
0758河台小赵 发表于 2004-9-14 16:03:00
列表视图控件本课中我们将学习如何创建和使用列表视图控件。 理论:

列表视图控件和树型视图、丰富文本编辑控件一样是通用控件的一种。可能您都已经知道了列表视图控件,只不过是不知道它的确切名字而已。列表视图控件可以用来很好地显示项目。在这方面它和列表框相同,只不过它的性能更强。 有两种方法创建一个列表视图控件。第一种也是最简单的方法是:用资源编辑器来创建它。用该种方法只是不要忘记在您的代码(的任何位置处)加入对InitCommonControls函数的调用(记得吗,调用该函数只是为了隐式地加载包含通用控件的DLL)。另一种方法是调用CreateWindowEx函数,这里您必须指定合适的类名,譬如:SysListView32,WC_LISTVIEW不是正确的类名 在列表视图种有四种方法来显示数据:大图标,小图标,列表和报告方式。这些方法和在资源管理器种选择View->Large Icons,Small Icons , List 和 Details 相对应。各种不同的显示方式只是显示了不同的外观而已。譬如,您可能有许多的数据,只是并不想全部显示。报告方式提供的消息最完全,其它的方式则要少得多。在刚创建一个列表视图时您可以选择一种初始显示方法,随后您可以调用SetWinodwLong函数并设置GWL_STYLE标志位来改变显示方式

既然我们已经知道了如何创建列表控件,接下来我们学习如何使用它们。我们将主要集中在报告方式的显示上,因为该种方式演示了最多的列表控制的特性。使用列表控制的步骤如下:

调用CreateWindowEx函数来创建一个列表控件,指定它的类名为SysListView32。您还可以在此处指定控件初次显示时的方式。 创建和初始化用在列表控件中显示项目的图象列表(如果存在)。 向列表控件中插入列,如果显示的方式是报告方式这一步是必须的。 向控件中插入项目和自项目。 列:

在报告方式中,有不止一个列。您可以把放入到列表控件中的数据看作是一张表单:这时数据是按行列排列的。在控件中至少有一列。在其它的显示方式中则无所谓,因为这些显示方式有仅有一列。 加入列要通过向列表控件发送LVM_INSERTCOLUMN消息来实现。

LVM_INSERTCOLUMN wParam = iCol lParam =指向LV_COLUMN型结构体变量的指针

iCol 列数,从0开始编号。 LV_COLUMN 包含了将插入的列的信息。它的定义如下:

LV_COLUMN STRUCT imask dd ? fmt dd ? lx dd ? pszText dd ? cchTextMax dd ? iSubItem dd ? iImage dd ? iOrder dd ? LV_COLUMN ENDS

Field nameMeaningsimask

一组标志位,它指示了该结构体中的那些成员变量是有效的。该结构体中的成员变量并不是同时有效的。在某些时候,可能只有某些成员变量是有效的。结构体可以用来输入和输出。这样让WINDOWS知道那些成员变量是有效的是非常重要的。可能的标志有:

LVCF_FMT = fmt有效 LVCF_SUBITEM = iSubItem有效 LVCF_TEXT = pszText有效. LVCF_WIDTH = lx有效

您可以一次使用几个标志。譬如,如果您向指定列的文本标签(列名),您必须在pszText成员变量中提供列名,然后指定标志LVCF_TEXT告诉WINDOWS成员变量pszText中的值是有效的,否则WINDOWS将忽略掉pszText中的值。

fmt

指定了项目/子项目的对齐方式。可能的值有:

LVCFMT_CENTER = 文本居中 LVCFMT_LEFT = 文本左对齐 LVCFMT_RIGHT = 文本右对齐

lxlx 是列的宽度(以像素点为单位)。以后您可以发送消息LVM_SETCOLUMNWIDTH来改变列的宽度。pszText如果用来设定列的属性时,该成员变量为指向列名的指针。如果是查询列名,该成员变量指向一个足够大的缓冲区,用来接收返回的列名,这是您必须在成员cchTextMax中指定缓冲区的大小。如果是设定列名时,可以忽略该变量,因为该指针指向的是一个ASCII码的字符串,而WINDOWS可以解析出ASCII串的长度。cchTextMaxcchTextMax 以字节计的上面一个成员变量指向的缓冲区的小。该成员变量只在您查询列的属性时使用。如果是设定列的属性,那该变量将被忽略。iSubItem指定和该列相连的子项目的索引号。该成员变量的值用来标识和列相连系的子项目。该列的使用最好地说明了如何把列号和子项目相连。要查询列的属性时可以发送LVM_GETCOLUMN消息,并在成员变量imask中指定LVCF_SUBITEM标志,列表控件将在iSubItem中返回插入时设定的iSubItem值。为了使用该办法,您需要在该成员变量中放入正确的值。 iImage and iOrder为了和IE3.0以上版本兼容。目前我没有这方面的资料。

在列表视图控件创建后,您必须至少向其中插入一列。当然如果不打算使用报告方式显示,那倒是没有必要插入列。为了插入列,您需要定义一个LV_COLUMN型的结构体变量,给其成员变量赋上正确的值,指定列号,然后向列表视图控件发送LVM_INSERTCOLUMN消息并把该结构体变量的值传过去。

LOCAL lvc:LV_COLUMN mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc

上面的代码段显示了该过程。当发送LVM_INSERTCOLUMN消息时,他指定了列的标题条文本和它的宽度。

项目和子项目

项目是列表视图中主要的内容。除报告方式显示的外,在列表视图您只能看到项目。子项目是项目的详细信息。一个项目可能有不止一个相关的子项目。举个例子,譬如项目是文件名,那其相关的子项目可能有文件属性、大小、创建日期等。在报告方式的视图中,最左边一列是项目,其它列是子项目。从数据库记录的角度看,项目类似主键,子项目类似记录。 至少您的列表视图需要一些项目:子项目是可选的。如果您想要给用户提供更多的信息,可以把子项目和项目相连,然后放到列表视图中以报告的方式显示。 您可以通过向列表视图发送LVM_INSERTITEM消息来向其中添加项目,这时还需要把一个指向LV_ITEM型的结构体的变量的指针放到lParam一同传给列表视图。LV_ITEM的定义如下:

LV_ITEM STRUCT imask dd ? iItem dd ? iSubItem dd ? state dd ? stateMask dd ? pszText dd ? cchTextMax dd ? iImage dd ? lParam dd ? iIndent dd ? LV_ITEM ENDS

Field nameMeaningsimask一组标志位标明该结构体中那些成员变量中的值有效。它的意义和上面我们提到的LV_COLUMN型结构体中向对应的成员变量基本相同。更详细的信息,可以查询WIN32 API 手册。iItem该结构体代表的项目的索引号。索引号是从0开始编号的。该值和表单的“行”类似。iSubItem和上一个成员变量指定的项目相连的子项目的索引号。您可以把它当作表单的“列”。譬如您想要把一个项目插入到新创建的列表视图控件,iItem的值应为0(因为该项目是第一个项目),iSubItem的值也应当为0(我们想把该项目插到第一列)。如果你想指定一个子项目和该项目相连,iItem中应该是您想要相连的项目的索引号,iSubItem的值应当是大于0的值,具体的值取决于您想把该子项目插在那一列。如果你的列表视图控件一共有4列的化,第一列包含了项目,其余3列是留给子项目的。如果您想把子项目插在第四列,应当指定该值为3。state

该成员变量包含的标志位反应了项目的状态。状态的改变可能是由用户的操作引起的或是程序改变的。这些状态包括:是否有焦点/高亮度显示/被选中(由于被剪切)/被选中等。另外还包括,以1为基数的索引用来代表是否处使用重叠/状态图标。

stateMask由于上面的成员变量包含状态标志位、重叠的位图索引号、和状态位图的索引号,我们需要告诉WINDOWS我们到底需要设定或查询那一个值。该成员变量就是用来做这项工作的。pszText当我们想设定项目的属性时,它包含项目名称的ASCII码的字符串的地址。当查询项目的属性时,该成员变量将用来接收查询返回的项目的名称。cchTextMax仅当您用来查询项目的属性时才需要使用该值,这时它包含上一个成员变量的大小。iImage图标在列表视图中的图象链表中的索引号。lParam用户定义的值,当您给项目排序时使用。当您告诉列表视图对项目排序时,列表视图将成对地比较项目。 它将会把两个项目的lParam的值传给您,这样您就可以进行比较先列出那一个了。如果您现在还不太明白的话,没有系,我们稍后还要讲关于排序的问题。

现在让我们来总结想列表控件中插入项目/子项目的步骤:

定义一个LV_ITEM型的结构体变量。 给该变量赋给合适的值 如果要插入一个项目,就向列表视图控件发送LVM_INSERTITEM值。 如果要插入一个子项目,发送LVM_SETITEM。如果您不明白项目和子项目之间的关系的话,可能会有一些疑惑。子项目仅是项目的属性而已,也就是说您可以插入一个项目但是不能插入一个子项目。所以添加一个子项目十只能发送LVM_SETITEM消息而不能发送LVM_INSERTITEM消息。 列表视图控件的消息/通知

既然您知道了如何创建和往其中添加内容,下一步就是如何和它通讯。列表视图控件和它的父窗口之间的通讯是通过消息/通知来进行的。父窗口通过发送消息来控制列表视图控件,列表视图控件通过发送WM_NOTIFY消息来通知它的父窗口。这一点和其它的通用控件没有什么不同。

排序项目/子项目

您可以在调用CreateWindowEx函数时指定LVS_SORTASCENDING 或 LVS_SORTDESCENDING风格来指定缺省的排序方式。这两种风格仅仅排序项目的名称。如果想要排序项目的其它属性,您可以通过发送LVM_SORTITEMS消息来完成

LVM_SORTITEMS wParam = lParamSort lParam = pCompareFunction

lParamSort 用户定义的值,该值将传递给用来比较的函数。 pCompareFunction 用户定义的用来比较排序的函数的地址。该函数的原型如下:

CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD

lParam1 和 lParam2 是 LV_ITEM型的结构体中的成员变量lParam的值。 lParamSort 是发送LVM_SORTITEMS消息时参数wParam中的值

当列表视图控件接收到LVM_SORTITEMS消息时,当需要比较项目时它会调用在lParam中指定的比较函数。比较函数将决定那一个项目排在前面。方法很简单:如果函数返回一个负值,由(lParam代表的)第一个项目排在前,如果返回正值,第二个项目排在前。如果相等,必须返回0 。

真正使得该方法能够运行的是LV_ITEM型结构体中的成员变量lParam值。当您需要排序时(譬如当您点击列的标题条时),您需要考虑好排序方案。在本例中,我们把项目的索引放到该成员变量中,这样我们可以通过发送LVM_GETITEM消息来得到项目的其它信息。注意:当项目重排序后,它们的索引也就变了。所以当重排序后,我需要在lParam参数中反应出新的索引。如果您想在用户点击列的标题条时重新排序,您需要在您的窗口过程函数中处理LVN_COLUMNCLICK通知消息。LVN_COLUMNCLICK消息是随同WM_NOTIFY消息一起发送的。

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

34#
0758河台小赵 发表于 2004-9-14 16:04:00
例子:

该例子创建了一个列表视图控件,并在其中显示了当前文件夹中的文件大小和文件名。缺省的视图是报告方式的,如果您点击列标题条,标题将按升/降序重新排列。您可以通过菜单选择不同的显示方式(大图标、小图标等)。当您双击一个项目时,项目的名称将显示在一个对话框中。

.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\comctl32.inc includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD IDM_MAINMENU equ 10000 IDM_ICON equ LVS_ICON IDM_SMALLICON equ LVS_SMALLICON IDM_LIST equ LVS_LIST IDM_REPORT equ LVS_REPORT RGB macro red,green,blue xor eax,eax mov ah,blue shl eax,8 mov ah,green mov al,red endm .data ClassName db "ListViewWinClass",0 AppName db "Testing a ListView Control",0 ListViewClassName db "SysListView32",0 Heading1 db "Filename",0 Heading2 db "Size",0 FileNamePattern db "*.*",0 FileNameSortOrder dd 0 SizeSortOrder dd 0 template db "%lu",0 .data? hInstance HINSTANCE ? hList dd ? hMenu dd ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT invoke ExitProcess,eax invoke InitCommonControls 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, NULL mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,IDM_MAINMENU 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,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst,NULL mov hwnd,eax invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .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 InsertColumn proc LOCAL lvc:LV_COLUMN mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc or lvc.imask,LVCF_FMT mov lvc.fmt,LVCFMT_RIGHT mov lvc.pszText,offset Heading2 mov lvc.lx,100 invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc ret InsertColumn endp ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD LOCAL lvi:LV_ITEM LOCAL buffer[20]:BYTE mov edi,lpFind assume edi:ptr WIN32_FIND_DATA mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0 lea eax,[edi].cFileName mov lvi.pszText,eax push row pop lvi.lParam invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi mov lvi.imask,LVIF_TEXT inc lvi.iSubItem invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow lea eax,buffer mov lvi.pszText,eax invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi assume edi:nothing ret ShowFileInfo endp FillFileInfo proc uses edi LOCAL finddata:WIN32_FIND_DATA LOCAL FHandle:DWORD invoke FindFirstFile,addr FileNamePattern,addr finddata .if eax!=INVALID_HANDLE_VALUE mov FHandle,eax xor edi,edi .while eax!=0 test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY .if ZERO? invoke ShowFileInfo,edi, addr finddata inc edi .endif invoke FindNextFile,FHandle,addr finddata .endw invoke FindClose,FHandle .endif ret FillFileInfo endp String2Dword proc uses ecx edi edx esi String:DWORD LOCAL Result:DWORD mov Result,0 mov edi,String invoke lstrlen,String .while eax!=0 xor edx,edx mov dl,byte ptr [edi] sub dl,"0" mov esi,eax dec esi push eax mov eax,edx push ebx mov ebx,10 .while esi > 0 mul ebx dec esi .endw pop ebx add Result,eax pop eax inc edi dec eax .endw mov eax,Result ret String2Dword endp CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD LOCAL buffer[256]:BYTE LOCAL buffer1[256]:BYTE LOCAL lvi:LV_ITEM mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 .if SortType==1 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke String2Dword,addr buffer mov edi,eax invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub edi,eax mov eax,edi .elseif SortType==2 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke String2Dword,addr buffer mov edi,eax invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub eax,edi .elseif SortType==3 mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer1,addr buffer .else mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer,addr buffer1 .endif ret CompareFunc endp UpdatelParam proc uses edi LOCAL lvi:LV_ITEM invoke SendMessage,hList, LVM_GETITEMCOUNT,0,0 mov edi,eax mov lvi.imask,LVIF_PARAM mov lvi.iSubItem,0 mov lvi.iItem,0 .while edi>0 push lvi.iItem pop lvi.lParam invoke SendMessage,hList, LVM_SETITEM,0,addr lvi inc lvi.iItem dec edi .endw ret UpdatelParam endp ShowCurrentFocus proc LOCAL lvi:LV_ITEM LOCAL buffer[256]:BYTE invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED mov lvi.iItem,eax mov lvi.iSubItem,0 mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 invoke SendMessage,hList,LVM_GETITEM,0,addr lvi invoke MessageBox,0, addr buffer,addr AppName,MB_OK ret ShowCurrentFocus endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .if uMsg==WM_CREATE invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL mov hList, eax invoke InsertColumn invoke FillFileInfo RGB 255,255,255 invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax invoke GetMenu,hWnd mov hMenu,eax invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED .elseif uMsg==WM_COMMAND .if lParam==0 invoke GetWindowLong,hList,GWL_STYLE and eax,not LVS_TYPEMASK mov edx,wParam and edx,0FFFFh push edx or eax,edx invoke SetWindowLong,hList,GWL_STYLE,eax pop edx invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED .endif .elseif uMsg==WM_NOTIFY push edi mov edi,lParam assume edi:ptr NMHDR mov eax,[edi].hwndFrom .if eax==hList .if [edi].code==LVN_COLUMNCLICK assume edi:ptr NM_LISTVIEW .if [edi].iSubItem==1 .if SizeSortOrder==0 || SizeSortOrder==2 invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc invoke UpdatelParam mov SizeSortOrder,1 .else invoke SendMessage,hList,LVM_SORTITEMS,2,addr CompareFunc invoke UpdatelParam mov SizeSortOrder,2 .endif .else .if FileNameSortOrder==0 || FileNameSortOrder==4 invoke SendMessage,hList,LVM_SORTITEMS,3,addr CompareFunc invoke UpdatelParam mov FileNameSortOrder,3 .else invoke SendMessage,hList,LVM_SORTITEMS,4,addr CompareFunc invoke UpdatelParam mov FileNameSortOrder,4 .endif .endif assume edi:ptr NMHDR .elseif [edi].code==NM_DBLCLK invoke ShowCurrentFocus .endif .endif pop edi .elseif uMsg==WM_SIZE mov eax,lParam mov edx,eax and eax,0ffffh shr edx,16 invoke MoveWindow,hList, 0, 0, eax,edx,TRUE .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp end start

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

35#
0758河台小赵 发表于 2004-9-14 16:05:00
分析:

当主窗口创建后要做的第一件事是创建一个列表视图控件应用程序。

.if uMsg==WM_CREATE invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL mov hList, eax

我们调用CreateWindowEx来创建窗口,并把窗口类的名称“SysListView32”传给它。缺省的显示方式是报告方式,因为您指定了LVS_REPORT标志作为它的风格。

invoke InsertColumn

创建列表视图控件后,我们向其中插入列。

LOCAL lvc:LV_COLUMN mov lvc.imask,LVCF_TEXT+LVCF_WIDTH mov lvc.pszText,offset Heading1 mov lvc.lx,150 invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc

我们指定第一列的宽度和列的标题条,为了在该列中显示文件的名称,我们需要在LV_COLUMN 型结构体变量的成员变量iMask中设定标志位LVCF_TEXT 或 LVCF_WIDTH。我们设定pszText为列标题条文本字符串的值,lx设定为列的宽度(以像素点为单位)。然后我们发送LVM_INSERTCOLUMN消息给列表视图控件,并把该结构体变量传递给它。

or lvc.imask,LVCF_FMT mov lvc.fmt,LVCFMT_RIGHT

插入完第一列后,我们再插入第二列,单击该列的标题条可以按文件的大小排序。因为我们需要右对齐文本,我们需要在成员变量fmt中指定标志位LVCFMT_RIGHT。我们还必须在成员变量iMask中除了标志位LVCF_TEXT 和 LVCF_WIDTH外还需要指定标志位LVCF_FMT。

mov lvc.pszText,offset Heading2 mov lvc.lx,100 invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc

剩余的代码比较简单。在pszText中放入文本字符串的地址,在lx中放入列的宽度。然后发送消息LVM_INSERTCOLUMN 给列表视图控件,在参数中同时传递列号和结构体变量的地址。

当插入完列后,我们向列表控件中加入项目。

invoke FillFileInfo

FillFileInfo 的代码如下:

FillFileInfo proc uses edi LOCAL finddata:WIN32_FIND_DATA LOCAL FHandle:DWORD invoke FindFirstFile,addr FileNamePattern,addr finddata

我们调用FindFirstFile来得到第一个符合搜索标准的的文件的信息。FindFirstFile函数的原型如下

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

pFileName 是用来匹配搜索的文件名的地址。该字符串包含了通配符。在我们的例子中是*.*,这样会搜索当前文件夹中所有的文件。 pWin32_Find_Data 是WIN32_FIND_DATA 型的结构体变量的地址,WIN32_FIND_DATA型的结构体变量将用来保存返回的文件的信息。

如果没有找到匹配的文件,该函数将在eax中返回INVALID_HANDLE_VALUE 。否则将返回一个搜索句柄,您可以用该句柄在FindNextFile函数中来搜索下一个符合条件的文件。

.if eax!=INVALID_HANDLE_VALUE mov FHandle,eax xor edi,edi

如果找到了一个文件,我们在一个变量中保存搜索句柄,并把寄存器edi清零,该寄存器将用作项目的索引号。

.while eax!=0 test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY .if ZERO?

在本课中,我们将不处理文件夹,所以我们检查dwFileAttributes成员变量的值是否有FILE_ATTRIBUTE_DIRECTORY 标志,如果有,我们就忽略掉它,然后调用FindNextFile。

invoke ShowFileInfo,edi, addr finddata inc edi .endif invoke FindNextFile,FHandle,addr finddata .endw

我们调用ShowFileInfo函数包文件的名称和大小信息加到列表视图控件中去。然后让edi寄存器加一来增加项目的行号。最后我们调用FindNextFile函数在当前文件夹中继续搜索文件一直到该函数返回0为止(这意味着没有可供搜索的文件了)。

invoke FindClose,FHandle .endif ret FillFileInfo endp

当前文件夹中的文件枚举完毕后,我们必须关闭搜索句柄。

先在我们看一下ShowFileInfo函数。该函数由两个参数,一个是项目的索引号(也即行号),另一个是WIN32_FIND_DATA型结构体变量的地址。

ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD LOCAL lvi:LV_ITEM LOCAL buffer[20]:BYTE mov edi,lpFind assume edi:ptr WIN32_FIND_DATA

把WIN32_FIND_DATA 型结构体变量的值放到寄存器edi中。

mov lvi.imask,LVIF_TEXT+LVIF_PARAM push row pop lvi.iItem mov lvi.iSubItem,0

我们将传递项目的名称和lParam的值,所以我们在iMask中放入标志位LVIF_TEXT 和LVIF_PARAM。接下来我们在iItem中放入传递进来的行号,另外由于这是主项目我们必须设置iSubItem的值等于0。

lea eax,[edi].cFileName mov lvi.pszText,eax push row pop lvi.lParam

我们现在要把标签字符串的地址,在这里也就是WIN32_FIND_DATA 型结构体变量中的文件的名称放到pszText中。由于我们要完成对项目的排序,所以必须设置lParam的值,我把它设成行号值,这样我们可以根据索引值来查询项目。

invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

设置完所有LV_ITEM型变量中的值后,我们发送LVM_INSERTITEM消息给列表视图控件来把项目插入到其中。

mov lvi.imask,LVIF_TEXT inc lvi.iSubItem invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow lea eax,buffer mov lvi.pszText,eax

我们将把子项目插入到第二列。一个子项目只能有一个标签。这样我们在iMask中指定LVIF_TEXT标志位。接着我们指定子项目所在的列,本例中我们通过将iSubItem加一使得该值等于1。标签值是文件的大小,为了转换成文本我们调用wsprintf函数,然后把文本的地址放到pszText中去。

invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi assume edi:nothing ret ShowFileInfo endp

当LV_ITEM型变量中的值设定好之后,我们向列表视图控件发送LVM_SETITEM消息,并一同把LV_ITEM变量的地址传过去。注意:发送的消息是LV_ITEM而不是LVM_INSERTITEM,因为我们插入的是子项目,子项目不是真正的项目而是主项目的属性。所以我们这时是在设定项目的属性,而不是加入一个项目。

当所有的项目都插入到列表视图控件后,我们设定它的文本和背景颜色。

RGB 255,255,255 invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax RGB 0,0,0 invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax

我们用RGB(R---Red G---Green B---Blue)来把三色转换并放到eax中。我们通过发送LVM_SETTEXTCOLOR 和 LVM_SETTEXTBKCOLOR 消息来设定文本的前景和背景色。

invoke GetMenu,hWnd mov hMenu,eax invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

我们将让用户通过菜单来选择它想要的显示方式。这样我们必须先得到菜单的句柄。我了让用户跟踪当前的视图,我们在菜单中放入一组单选按钮。我们可以调用CheckMenuRadioItem函数,该函数将把一个单选按钮放到一个菜单项前。

注意我们创建列表视图控件时把它的宽度和高度都设成为0。当父窗口改变大小时,它将同时改变大小。这样我们可以让列表视图总是随着主窗口改变。子我们的例子中,我们让列表视图填充整个客户区。

.elseif uMsg==WM_SIZE mov eax,lParam mov edx,eax and eax,0ffffh shr edx,16 invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

当父窗口接收到了WM_SIZE消息后,lParam的底字部分包含了客户区新的宽和高。让后我们调用MoveWindow来改变列表视图控件的大小使得它覆盖整个的客户区。

当用户通过菜单选择了一种选择方式,我们必须相应地改变列表视图中的显示方式。我们调用SetWindowLong函数来设定新的风格。

.elseif uMsg==WM_COMMAND .if lParam==0 invoke GetWindowLong,hList,GWL_STYLE and eax,not LVS_TYPEMASK

首先得到当前的风格,然后清除旧的风格。LVS_TYPEMASK 是LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT四种风格的集合。这样当我们用当前的风格“与”“not LVS_TYPEMASK”就等于清除了当前的显示风格。

在设计菜单时,我们使用了一些小技巧。我们包显示风格的常数串当作菜单的ID号。

IDM_ICON equ LVS_ICON IDM_SMALLICON equ LVS_SMALLICON IDM_LIST equ LVS_LIST IDM_REPORT equ LVS_REPORT

这样当父窗口接收到WM_COMMAND消息时,希望显示的风格值会当成菜单的ID号传递过来。

mov edx,wParam and edx,0FFFFh

在wParam中的低字部分是欲显示的风格,我们所需要做的只是把高字部分清0。

push edx or eax,edx

我们把希望显示的风格加到列表视图的风格中去(已经去除了旧的风格)。

invoke SetWindowLong,hList,GWL_STYLE,eax

调用SetWindowLong函数来设定新的风格。

pop edx invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED .endif

我们需要在被选择的显示方式前放入单选按钮。如果要排序,我们必须处理WM_NOTIFY消息。

.elseif uMsg==WM_NOTIFY push edi mov edi,lParam assume edi:ptr NMHDR mov eax,[edi].hwndFrom .if eax==hList

当我们接收到了WM_NOTIFY 消息后,lParam包含了指向NMHDR型结构体变量的指针。我们通过把列表视图控件的值和NMHDR型结构体变量中的hwndFrom成员变量的值比较来判断,如果相等的话我们就可以确定消息是列表视图控件发送的。

.if [edi].code==LVN_COLUMNCLICK assume edi:ptr NM_LISTVIEW

如果通知消息是列表视图控件发送的,我们检测该消息是否是LVN_COLUMNCLICK。如果是,它意味着用户点击了列标题条。在接收到LVN_COLUMNCLICK消息后,我们假设lParam参数包含NM_LISTVIEW型结构体变量的指针,NM_LISTVIEW型结构体是NMHDR型结构体的扩展。我们需要知道用户单击了那一列,在iSubItem中的值即是列号,列的编号是从0开始的。

.if [edi].iSubItem==1 .if SizeSortOrder==0 || SizeSortOrder==2

在这里iSubItem的值是1,它表示用户点击的是第二列,即文件的大小。我们用状态变量来保持当前的排序顺序。0代表不用排序,1代表升序,2代表降序。如果该列中的项目/子项目以前没有排序或为降序,我们就把它设成升序。

invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

我们发送消息LVM_SORTITEMS给列表视图控件,在wParam中传递1,在lParam中传递比较函数的参数。注意wParam中的值是用户定义的,用户可以按自己的需要来解释,这里我们把它用作排序的方法。我们先来看看比较函数:

CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD LOCAL buffer[256]:BYTE LOCAL buffer1[256]:BYTE LOCAL lvi:LV_ITEM mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256

列表视图控件将传递需要比较的两个项目的lParam(LV_ITEM型结构体变量的成员变量)比较函数。您还记得吗?我们在lParam中放置了醒目的索引号。这样我们利用这些索引号查询列表视图来得到项目信息。我们需要的消息是项目/子项目的标签文本。为此我们准备好LV_ITEM 型结构体变量并在iMask中设置标志位LVIF_TEXT ,在pszText中设置缓冲区的地址,在cchTextMax中设置缓冲区的大小。

.if SortType==1 mov lvi.iSubItem,1 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi

如果SortType的值为1或2,我们知道点击了那一列,1代表根据文件的大小按升序排列所有的项目。2的意思相反。这样我们指定iSubItem为1(代表文件大小列)然后发送LVM_GETITEMTEXT 消息给列表视图控件来得到在项目的标签文本串。

invoke String2Dword,addr buffer mov edi,eax

调用子定义的String2Dword函数来把字符串转换成一个DWORD值。它将在eax中返回转换后的值,我们把它保存在edi中以便以后比较用。

invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke String2Dword,addr buffer sub edi,eax mov eax,edi

对lParam2 中的值做同样的操作。当我们得到了两个文件的大小后,就可以比较它们了。比较的规则如下:

如果第一个项目放在前面,在eax中返回一个负值 如果第二个项目放在前面,在eax中返回一个正值 如果相等,在eax中返回0

在我们这里,我们想按升序排列,所以我们只要简单地将第二个项目的文件大小减去第一个项目的文件大小,然后返回放在eax中的值。

.elseif SortType==3 mov lvi.iSubItem,0 invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi invoke lstrcpy,addr buffer1,addr buffer invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi invoke lstrcmpi,addr buffer1,addr buffer

当用户点击文件名字列时,我们必须比较文件的名字。我们先得到文件的名字,然后调用lstrcmpi函数来比较,然后只要简单返回lstrcmpi的值,因为该函数比较使用的规则和我们的相同。

当项目排序后,我们调用UpdateParam函数来更新所有项目的lParam的值来反应出最新的改变。

invoke UpdatelParam mov SizeSortOrder,1

该函数简单地枚举列表视图中所有的项目并且把它们lParam更新成项目的索引号。

.elseif [edi].code==NM_DBLCLK invoke ShowCurrentFocus .endif

如果用户双击某个项目时,我们将显示一个消息框,上面有该项目的有关标签值。我们必须检查NMHDR 中的code值是否是 NM_DBLCLK。如果是,我们就得到它的标签值并显示在一个消息框中。

ShowCurrentFocus proc LOCAL lvi:LV_ITEM LOCAL buffer[256]:BYTE invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

我们是增么怎么知道某个项目被双击的呢?当单击或双击某个项目时,它的状态被设成“焦点”。即使有多个项目被选中,也仅有一个项目有焦点。我们的工作就是去找到那个有焦点的项目。我们发送LVM_GETNEXTITEM消息给列表视图控件,在lParam中指定期望的状态。如果wParam中时-1的话,表示要搜索所有的项目。有焦点的项目第索引号在eax中返回。

mov lvi.iItem,eax mov lvi.iSubItem,0 mov lvi.imask,LVIF_TEXT lea eax,buffer mov lvi.pszText,eax mov lvi.cchTextMax,256 invoke SendMessage,hList,LVM_GETITEM,0,addr lvi

发送LVM_GETITEM消息来得到标签。

invoke MessageBox,0, addr buffer,addr AppName,MB_OK

最后我们在一个消息框中显示标签。

如果想在列表视图控件中显示图标,您可以阅读关于树型视图控件的课程。它们的步骤基本上是一样的。

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

36#
0758河台小赵 发表于 2004-9-14 16:06:00
例子:.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .const IDR_MAINMENU equ 101 IDR_CHILDMENU equ 102 IDM_EXIT equ 40001 IDM_TILEHORZ equ 40002 IDM_TILEVERT equ 40003IDM_CASCADE equ 40004 IDM_NEW equ 40005 IDM_CLOSE equ 40006 .data ClassName db "MDIASMClass",0 MDIClientName db "MDICLIENT",0 MDIChildClassName db "Win32asmMDIChild",0 MDIChildTitle db "MDI Child",0 AppName db "Win32asm MDI Demo",0 ClosePromptMessage db "Are you sure you want to close this window?",0.data? hInstance dd ? hMainMenu dd ? hwndClient dd ? hChildMenu dd ? mdicreate MDICREATESTRUCT <> hwndFrame 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 ;============================================= ; 注册框架窗口类 ;============================================= mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc,OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_APPWORKSPACE mov wc.lpszMenuName,IDR_MAINMENU 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 ;================================================ ; 注册MDI子窗口类 ;================================================ mov wc.lpfnWndProc,offset ChildProc mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszClassName,offset MDIChildClassName invoke RegisterClassEx,addr wc invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,\ hInst,NULL mov hwndFrame,eax invoke LoadMenu,hInstance, IDR_CHILDMENU mov hChildMenu,eax invoke ShowWindow,hwndFrame,SW_SHOWNORMAL invoke UpdateWindow, hwndFrame .while TRUE invoke GetMessage,ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMDISysAccel,hwndClient,addr msg .if !eax invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endif .endw invoke DestroyMenu, hChildMenu mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL ClientStruct:CLIENTCREATESTRUCT .if uMsg==WM_CREATE invoke GetMenu,hWnd mov hMainMenu,eax invoke GetSubMenu,hMainMenu,1 mov ClientStruct.hWindowMenu,eax mov ClientStruct.idFirstChild,100 INVOKE CreateWindowEx,NULL,ADDR MDIClientName,NULL,\ WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\ hInstance,addr ClientStruct mov hwndClient,eax ;======================================= ; 初始化MDICREATESTRUCT结构 ;======================================= mov mdicreate.szClass,offset MDIChildClassName mov mdicreate.szTitle,offset MDIChildTitle push hInstance pop mdicreate.hOwner mov mdicreate.x,CW_USEDEFAULT mov mdicreate.y,CW_USEDEFAULT mov mdicreate.lx,CW_USEDEFAULT mov mdicreate.ly,CW_USEDEFAULT .elseif uMsg==WM_COMMAND .if lParam==0 mov eax,wParam .if ax==IDM_EXIT invoke SendMessage,hWnd,WM_CLOSE,0,0 .elseif ax==IDM_TILEHORZ invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0 .elseif ax==IDM_TILEVERT invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0 .elseif ax==IDM_CASCADE invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0 .elseif ax==IDM_NEW invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate .elseif ax==IDM_CLOSE invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0 invoke SendMessage,eax,WM_CLOSE,0,0 .else invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam ret .endif .endif .elseif uMsg==WM_DESTROY invoke PostQuitMessage,NULL .else invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam ret .endif xor eax,eax ret WndProc endp ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_MDIACTIVATE mov eax,lParam .if eax==hChild invoke GetSubMenu,hChildMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx .else invoke GetSubMenu,hMainMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx .endif invoke DrawMenuBar,hwndFrame .elseif uMsg==WM_CLOSE invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO .if eax==IDYES invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0 .endif .else invoke DefMDIChildProc,hChild,uMsg,wParam,lParam ret .endif xor eax,eax ret ChildProc endp end start 分析:

程序所做的第一件事情就是注册框架窗口类和MDI子窗口类. 作完这个以后, 程序调用CreateWindowEx来创建框架窗口.用框架窗口的WM_CREATE句柄来创建客户窗口:

LOCAL ClientStruct:CLIENTCREATESTRUCT .if uMsg==WM_CREATE invoke GetMenu,hWnd mov hMainMenu,eax invoke GetSubMenu,hMainMenu,1 mov ClientStruct.hWindowMenu,eax mov ClientStruct.idFirstChild,100 invoke CreateWindowEx,NULL,ADDR MDIClientName,NULL,\ WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\ hInstance,addr ClientStruct mov hwndClient,eax

我们调用GetMenu来获得框架窗口的菜单的句柄, 这个句柄在调用GetSubMenu时将会被用到. 注意我们将1传递给 GetSubMenu ,因为我们希望显示窗口列表的子菜单是第二个子菜单. 然后我们给CLIENTCREATESTRUCT结构的成员赋值. 下一步我们初始化MDICLIENTSTRUCT 结构. 注意我们不需要在这里做.要初始化MDICLIENTSTRUCT结构的话,用WM_CREATE消息比较方便.

mov mdicreate.szClass,offset MDIChildClassName mov mdicreate.szTitle,offset MDIChildTitle push hInstance pop mdicreate.hOwner mov mdicreate.x,CW_USEDEFAULT mov mdicreate.y,CW_USEDEFAULT mov mdicreate.lx,CW_USEDEFAULT mov mdicreate.ly,CW_USEDEFAULT

在框架窗口创建之后(也包括客户窗口), 我们调用LoadMenu从资源中获取子窗口的菜单.我们需要获取这个菜单的句柄,这样当一个MDI子菜单出现时,我们就可以用这个句柄来取代框架窗口的菜单. 在这个应用程序退出到Windows之前不要忘记调用DestroyMenu来去掉这个句柄. 正常情况下当一个应用程序退出的时候,Windows将会自动释放和窗口相关的菜单. 但是在这种情况下, 子窗口的菜单没有和任何窗口相关联, 这样即使当应用程序退出后半部 子窗口的菜单仍然会占用宝贵的内存资源.

 

invoke LoadMenu,hInstance, IDR_CHILDMENU mov hChildMenu,eax ........ invoke DestroyMenu, hChildMenu

在消息循环中我们调用TranslateMDISysAccel.

.while TRUE invoke GetMessage,ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMDISysAccel,hwndClient,addr msg .if !eax invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endif .endw

假如TranslateMDISysAccel 返回一个非零值, 它以为着Windows已经在处理这条消息.这样你就不需要为这条消息做任何事情了.假如返回的值为零, 那么这条消息就不是MDI相关的消息, 因此就必须按照通常情况来处理.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM ..... .else invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam ret .endif xor eax,eax retWndProc endp

注意到在框架窗口的窗口过程中, 我们调用DefFrameProc来处理我们不感兴趣的消息.

窗口过程的重要之处在 WM_COMMAND句柄. 当用户从文件菜单中选择 "New"时, 我们就创建了一个MDI子窗口.

.elseif ax==IDM_NEW invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate

在我们的例子中,我们通过发送WM_MDICREATE消息给客户窗口, 同时还要在lParam参数中传递MDICREATESTRUCT结构的地址来创建一个MDI子窗口.

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD .if uMsg==WM_MDIACTIVATE mov eax,lParam .if eax==hChild invoke GetSubMenu,hChildMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx .else invoke GetSubMenu,hMainMenu,1 mov edx,eax invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx .endif invoke DrawMenuBar,hwndFrame

当MDI子窗口创建后, 我们可以监视 WM_MDIACTIVATE以察看它是不是一个活动窗口.具体的方法是比较将某一个MDI子窗口的句柄和参数lParam的值进行比较, 参数lParam中包含的是活动子窗口的句柄. 这样如果两者匹配的话, 证明这个MDI子窗口就是活动子窗口. 下一步就是将框架窗口的菜单替换成MDI子窗口自身的菜单. 因为最初的菜单将会被取代, 你比学赶帮超再一次告讼Windows窗口列表将显示在哪一个子菜单. 这就是我们必须再一次调用GetSubMenu 来检索子菜单的句柄的原因. 我们发送 WM_MDISETMENU消息给客户窗口来获得想要的结果. WM_MDISETMENU中的wParam参数包含了你希望取代最初的菜单的菜单句柄. lParam参数包含的是你希望用来显示窗口列表的子菜单的句柄.在发送了WM_MDISETMENU之后, 我们调用l DrawMenuBar 来刷新菜单, 否则的话你的菜单将会是一片混乱.

.else invoke DefMDIChildProc,hChild,uMsg,wParam,lParam ret .endif 在MDI子窗口的窗口过程中, 你必须传送所有未处理的消息给DefMDIChildProc而不是DefWindowProc. .elseif ax==IDM_TILEHORZ invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0 .elseif ax==IDM_TILEVERT invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0 .elseif ax==IDM_CASCADE invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0

当用户在窗口子菜单中选择一个菜单项时, 我们发送相应的消息给客户窗口. 假如用户选择平铺窗口, 我们发送 WM_MDITILE 给客户窗口, 在wParam参数中指定哪一种类型的平铺. 选择重叠的话是类似的, 相应的发送WM_MDICASCADE..

.elseif ax==IDM_CLOSE invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0 invoke SendMessage,eax,WM_CLOSE,0,0

假如用户选择 "Close" 菜单项, 我们首先必须通过发送WM_MDIGETACTIVE给客户窗口来获得当前活动的MDI子窗口的句柄, 返回的值保存在eax寄存器中, 这个值就是当前活动MDI子窗口的句柄. 获得句柄之后, 我们就可以发送WM_CLOSE给那个窗口了.

.elseif uMsg==WM_CLOSE invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO .if eax==IDYES invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0 .endif

在MDI子窗口的窗口过程中, 当收到WM_CLOSE的消息时, 就会显示一个消息框询问用户是否确实想关闭着这个窗口. 假如回答是"是"的话, 我们发送WM_MDIDESTROY给客户窗口. WM_MDIDESTROY关闭MDI子窗口,然后恢复框架窗口的标题.

回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

37#
reqidong 发表于 2004-9-14 16:45:00
小赵灌起水来我看无人能敌!
回复 支持 反对

使用道具 举报

新浪微博达人勋

千家认证

38#
0758河台小赵 发表于 2004-9-14 19:19:00

这贴有用的人不多,看来删了它吧。

[此贴子已经被作者于2004-9-14 21:48:57编辑过]
回复 支持 反对

使用道具 举报

12
您需要登录后才可以回帖 登录 | 立即注册 新浪微博登陆 千家通行证登陆

本版积分规则

千家智客微信号
千家智客微信
玩物说商城
玩物说商城