Win32汇编教程 上

类别:编程语言 点击:0 评论:0 推荐:

1.    Win32汇编的环境和基础
1.32位环境简介

  在Dos下编汇编程序,我们可以管理系统的所有资源,我们可以改动系统中所有的内存,如自己改动内存控制块来分配内存,自己修改中断向量表来截获中断等,对其他操作也是如此,如我们对键盘端口直接操作就可以把键盘屏蔽掉,可以这样来描述Dos系统:系统只有一个特权级别,在编程上讲,任何程序和操作系统都是同级的,所以在Dos下,一个编得不好的程序会影响其他所有的程序,如一个程序把键盘口中断关掉了,所有程序就都不能从键盘获得键入的数据,直到任何一个程序重新打开键盘为止,一个程序陷入死循环,也没有其他程序可以把它终止掉。Dos下的编程思路是“单任务”的,你只要认为你的程序会按照你的流程一步步的执行下去,不必考虑先后问题(当然程序可能会被中断打断,但你可以认为它们会把环境恢复,如果中断程序没有把环境恢复,那是他们的错)。
  在内存管理方式上,Dos汇编和Win32汇编也有很多的不同:Dos工作在实模式下,我们可以寻址1M的内存,寻址时通过段寄存器来制定段的初始地址,每个段的大小为64K,超过1M的部分,就只能把他作为XMS使用,也就是说,只能用作数据存放使用而无法在其中执行程序。
  而Windows在保护模式下执行,这里所有的资源对应用程序来说都是被“保护”的:程序在执行中有级别之分,只有操作系统工作在最高级--0级中,所有应用程序都工作在3级中(Ring3), 在Ring3中,你无法直接访问IO端口,无法访问其他程序运行的内存,连向程序自己的代码段写入数据都是非法的,会在Windows的屏幕上冒出一个熟悉的蓝屏幕来。只有对Ring0的程序来说,系统才是全开放的。
  在内存方面,Windows使用了处理器的分页机制,使得对应用程序来说,所有的内存都是“平坦”的,你不必用一个段寄存器去指定段的地址,因为在保护模式下,段寄存器的含义是不同的(可以参见80386手册方面的书籍),你可以直接指定一个32位的地址来寻址4GB的内存。
  在程序结构方面,Windows程序也有很大的不同,它是“基于消息”的,你可以想象这样一个常见的Windows窗口,上面有几个按钮,如果你用Dos编程的思路去考虑,你会发现实现它很困难:鼠标移动到窗口边缘时拖动会改变窗口大小,鼠标点击按钮时再做要做的事,你会发现,你的程序自开始执行后就在等待,你不知道鼠标先会点什么地方,实际上你是在等待所有可能的事情的发生。而在Dos下,你可以只顾自己先执行,需要用户输入时,再停下来,你不输入我就不再执行,而且,我让你输入数据A你就不能输入数据B。
  好了,言归正传,因为以上是Win32编程的基础,无论对Win32汇编还是VC++,它们都是一样的,下面我们来看看有关Win32汇编的内容。

2.Win32ASM编译器

  Win32ASM的编译器最常用的有两种:Borland公司的Tasm5.0和Microsoft的Masm6.11以上版本,两种编译器各有自己的优缺点,Tasm带了一个不大不小的Import库,而Masm没有带,但Masm在代码的优化上面好象比Tasm做得好,但它却不带Import库。看来使用哪一种编译器还是比较难选择的,但Steve Hutchesson给了我们一个答案,他为Masm建立了一个很全的Import库,基本上包括了Windows绝大部分的Api函数,这些库、include文件和其他工具还有Masm6.14版本一起做成了一个 Masm32编译器 -- Masm32V5。这样一来,我们用汇编编程就象用C一样方便。
  因为有了Masm32V5,所以就我个人而言,我推荐使用Masm作为Win32ASM的编译工具,但Masm和Tasm的宏语法有很多的不同,我的这个教程是以Masm格式写的。

3.Masm32的环境设置

  在Win32编程中,由于Windows有很多的数据结构和定义,这些都放在include文件中,还有连接时要用到Import库(通俗的讲就是Windows提供的DLL文件中的函数列表,也就是告诉程序到哪里去调用API函数),这些都放在include 和lib目录中。我们在编译时要指定以下的系统环境:

set include=\Masm32v5\Include
set lib=\Masmv5\lib
set path=\Masmv5\Bin

这样编译器就会到正确的路径中去找 include 文件和 lib 文件。你可以自己在 autoexec.bat 文件中加上以上语句,为了产生Windows的PE格式的执行文件,在编译和连接中要指定相应的参数:

编译: Ml /c /coff 文件名.asm
连接: Link /SUBSYSTEM:WINDOWS OBJ文件名.obj 资源文件名.res

为了不在每次编译时都要打这么多的参数,我们可以用 nmake 文件来代为执行,nmake 是代码维护程序,他会检查 .asm .obj .exe .res 等文件的时间,如果你更新了源程序,他会自动执行编译程序或连接程序产生相应的文件。你可以在文件名为 makefile 的文件中指定使用的编译器和连接程序以及相应的参数,下面是一个 makefile 文件的例子:

NAME = Clock
OBJS = $(NAME).obj
RES = $(NAME).res
$(NAME).exe: $(OBJS) $(RES)
Link /DEBUG /SUBSYSTEM:WINDOWS $(OBJS) $(RES)
$(RES): $(NAME).rc
Rc $(NAME).rc
.asm.obj:
Ml /c /coff $(NAME).asm

文件告诉 nmake程序,程序名为 clock,产生 clock.exe 文件需要 clock.obj和 clock.res 文件,而产生 clock.res 文件需要 clock.rc 文件,产生 clock.obj 文件要用到 clock.asm 文件,至于是否需要执行 ml, link 和 rc,程序会根据文件的时间自动判断。


2.    Win32ASM程序的结构和语法

  让我们先来看看一个最简单的Win32汇编程序:

.386 .model flat, stdcall option casemap :none ; case sensitive include windows.inc include kernel32.inc includelib kernel32.lib .data szCaption db 'Win32汇编例子',0 szText db 'Win32汇编,Simple and powerful!',0 .code start: invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK invoke ExitProcess,NULL end start

这就是一个能执行的最简单的Win32汇编程序,下面我简单地介绍一下各部分的作用:

.386

这条语句和Dos下汇编是一样的,是告诉编译器我们要用到80386的指令集,因为32位汇编程序要用到32位的寄存器如eax,ebx等,所以这一句是必须的,当然,你也可以用.486,.586等,当用到特权指令时,还可以用 .386p,.486p等等。

.model flat,stdcall

.model告诉编译器程序的模式,编过Dos汇编的人可能知道在Dos程序的模式有tiny,small,...huge 等,它指定了程序内存寻址模式,在huge等模式下,内存寻址和子程序调用将用Far的格式,但在Win32汇编中,你只能使用一个模式即 flat 模式,因为对Win32程序来说,内存是连续的一个4GB的段,无所谓小或大的模式。而stdcall 告诉编译器参数的传递方式,在调用子程序时,参数是通过堆栈传递的,参数的传递方式有三种,stdcall,c 和 pascal,stdcall 指定了参数是从右到左压入堆栈的,比如说对一个Windows API 如 MessageBox,在手册中是如此定义的:

int MessageBox( HWND hWnd, // handle of owner window LPCTSTR lpText, // address of text in message box LPCTSTR lpCaption, // address of title of message box UINT uType // style of message box );

那么在汇编中我们就可以这样调用它:

push uType push lpCaption push lpText push hWnd call MessageBox

大家要注意最右面的参数是最后一个进堆栈的,当然,我们不必这样麻烦的调用一个 API,因为Masm中的一个宏语句不但帮助我们完成了所有的压栈操作,还帮我们检查参数的个数是否正确,那就是 invoke 语句,我们可以把上面的语句换成 invoke MessageBox,hWnd,lpText,lpCaption,uType 就行了。如本程序中代入实际参数就成了 invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK。

include 语句

include 语句包含了一些系统的定义和API函说明,其中所有的Windows 数据结构定义和常量定义包含在 windows.inc 中,而其他 API函数的说明包含在 xxx.inc 中, 如查 Microsoft Win32 Programmer's Reference 知道 ExitProcess包含在kernel32.dll 中,那么我们就要在程序中包括 include kernel32.inc 和 includelib kernel32.lib语句,否则在编译时会出现 API 函数未定义的错误。而 MessageBox 在 user32.dll 中,那么我们就要在程序中包括 include user32.inc 和 includelib user32.lib语句

.data 或 .data?

指明了接下来是数据段,.data 定义了预定义的变量,.data?定义了未初始化的变量,两者的不同之处是 .data? 定义的变量并不占用 .exe 文件的大小,而是在程序执行时动态分配,所以开始是不指定初始值的数据可以放在 .data? 段中,如一个1K大小的缓冲区,放在 .data?中,程序将不会增加一个字节。

.code

指明了接下来是代码段,我们的所有代码都放在这里。最后的一句 start 语句指定了程序开始执行的语句。程序中的 ExitProcess 是一个标准的 Win32 API,对应 Dos汇编中的 int 20h 或 mov ah,4ch/int 21h,也就是程序退出。而 MessageBox 也是一个标准的 API,功能是在屏幕上显示一个消息框,具体的参数上面已经解释过了还有要注意的是 invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK 语句中, MB_OK 和 NULL 已经预定义在 Windows.inc 中。



3.     一个简单的对话框 --- 兼谈资源文件的使用
Windows 的资源文件

    不管在Dos下编程还是在Windows下编程,我们总是要用到除了可执行文件外的很多其他数据,如声音数据,图形数据,文本等等,在Dos下编程,我们可以自己定义这些文件的格式,但这样一来就造成了很多资源共享的问题,大家可能还记的Dos下的很多游戏,它们的图形都是按自己的格式存放的,你无法用标准的看图软件来看。也无法把它另存为其他格式。虽然在Win32编程中,我们仍然可以这样做,但Win32编程给了我们一个方案 ---- 就是格式统一的资源文件,把字符串、图形、对话框包括上面的按钮,文本等定义到一个资源文件中,就可以方便的在不同的文件中使用它,最重要的是,如果我们用自己的文件格式,使用时就要涉及到这些文件的读写操作,比较复杂,但使用资源文件时,Windows提供了一系列的API来装入资源。非常方便。现在,让我们来看一个很简单的资源文件的源文件,它的扩展名是 .rc,当它用资源编译器编译以后产生 .res 文件就可以在 link的时候连入.exe 文件中:

#include <Resource.h> #define DLG_MAIN 1 DLG_MAIN DIALOGEX 0, 0, 236, 185 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "对话框模板" FONT 9, "宋体" BEGIN DEFPUSHBUTTON "退出",IDOK,177,163,50,14 CONTROL "",-1,"Static",SS_ETCHEDHORZ,7,155,222,1 END

现在我简单解释一下 .rc文件的语法:

#include <resource.h> -- resource.h文件包括资源文件的一些常量定义,如下面的 WS_POPUP,WS_VISIBLE 等窗口的风格等等

#define DLG_MAIN 1 -- 类似于 .asm 文件的 equ 语句,和汇编源程序一样,这些定义是为了程序的可读性。

DLG_MAIN DIALOGEX 0,0,236,185

Windows的.rc文件可以定义 BITMAP(位图),CURSOR(光标),ICON(图标),ACCELERATORS(加速键),DIALOG(对话框),MENU(菜单),STRINGTABLE(字符串表),RCDATA(自定义资源)等8种资源,详细的描述可以参考有关MFC的书籍,在Win32ASM中的资源编译器的语法中,一般格式是这些资源的定义方法是:

位图定义: nameID BITMAP [load-mem] filename
光标定义: nameID CURSOR [load-mem] filename
图标定义: nameID ICON [load-mem] filename
加速键定义:
acctablename ACCELERATORS [optional-statements]
BEGIN event, idvalue, [type] [options]
. . .
END

等等,具体的定义和参数可以参考 Masm32v5 中的 Rc.hlp 帮助文件。(可以在编程工具中下载),我们可以用资源编辑器来所见即所得地编辑资源,也可以在文本编辑器中用上面这些语句自己定义资源。

在程序中使用资源

    在程序中,要使用资源之前必须先装如内存,Windows定义了一系列的API来装入资源,如 LoadMenu,LoadString,LoadBitmap 等等,如 LoadBitmap 的定义:
HBITMAP LoadBitmap(
HINSTANCE hInstance, // handle of application instance
LPCTSTR lpBitmapName // address of bitmap resource name
);
    这些Load函数的返回值是一个句柄,调用参数中一般至少为两项: hInstance 和 ResouceName,这个 ResouceName(如BitmapName,MenuName)就是在资源文件中的 #define 指定的值,如果你用 #define MY_ICON 10/ MY_ICON ICON "Main.ico" 定义了一个图标,那么在程序中要使用 Main.ico 图标就可以用 LoadIcon(hInstance,10) 来装入已经定义为10号的图标文件。另一个参数 hInstance 是执行文件的句柄,它对应资源所在的文件名,你可以在程序开始执行时用 invoke GetModuleHandle,NULL 获得 hInstance。另外一些资源并不是显式地装入的,如对话框资源,它是在建立对话框的函数中由Windows自己装入的,如下面例子中的 invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 ,是在屏幕上显示一个资源文件中已经定义好了的对话框,就并不存在 LoadDialogBox 之类的API来先装入对话框。

Win32ASM - 显示一个对话框

介绍了这么多相关的东西,现在让我们来看看如何显示一个对话框,源程序如下:

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Programmed by 罗云彬, [email protected] ; Website: http://asm.yeah.net ; LuoYunBin's Win32 ASM page (罗云彬的编程乐园) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat, stdcall option casemap :none ; case sensitive include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib DLG_MAIN equ 1 .data? hInstance dd ? szBuffer db 256 dup (?) _ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD .data .code ;******************************************************************** _ProcDlgMain proc uses ebx edi esi, \ hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD mov eax,wMsg .if eax == WM_CLOSE invoke EndDialog,hWnd,NULL .elseif eax == WM_INITDIALOG .elseif eax == WM_COMMAND mov eax,wParam .if eax == IDOK invoke EndDialog,hWnd,NULL .elseif eax == IDCANCEL invoke EndDialog,hWnd,NULL .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret _ProcDlgMain endp ;******************************************************************** start: invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 invoke ExitProcess,NULL end start

看了前面几篇文章以后,这儿的大部分语句应该是很熟悉了,我来讲解几句新的语句:

_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

PROTO 语句类似于C语言中的函数定义,在Win32汇编中,如果子程序的定义在引用以后,你就必须先定义,当然,这个定义是针对 invoke 语句和其他带参数的调用的,如果你的子程序没有参数,你就可以用 call 指令去调用它而不是用宏指令 invoke,这时候你就不必声明这个函数。

_ProcDlgMain proc uses ebx edi esi, \
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

这个定义 proc 的语句应该是不陌生的,要重复讲解一下的是 uses 和 下面的参数,uses 下的寄存器表示要编译器自动插入保存及恢复这些寄存器的指令,\ 是在 Masm32 中接下一行的符号,表示下一行是本行的继续内容,以避免一行中的内容过长。下面的 hWnd:DWORD 等语句定义了调用这个子程序的参数,如果有以下定义 MyProc proc dwPara1:DWORD,dwPara2:DWORD,dwPara3:DWORD,然后你用 invoke MyProc 1,2,3 来调用它,那么,1,2,3 将分别被赋值给 dwPara1,dwPara2,dwPara3,你可以在子程序中使用这些传递过来的参数。如果参数的类型是双字,那么:DWORD 可以省略。

.if/.else/.elseif/.endif

这些语句是宏指令,实际上不说你也知道它们的意思,有了这些宏指令,我们就可以把汇编编得象C一样结构清晰,而不必老是看到 jmp 指令了,当然,这只不过编译器帮你做了这些事情而已,如果你去反汇编一下,你开始会看到一大堆 jmp 指令,.if 的格式如下
.if eax == 1 如果eax等于1
.if eax != 1 如果eax不等于1
.if eax != 1 && ebx != 2 如果eax不等于1且ebx不等于2
.if eax == 1 || ebx == 2 如果eax等于1或者ebx等于2
其他的宏指令还有 .while/.endw .break 等等,可以参考 Masm32V5 的帮助文件 Masm32.hlp

    最后要讲到的就是 DialogBoxParam 这个API了,在Windows中,所有的窗口都要指定一个子程序,当Windows检测到鼠标、定时器等和这个窗口有关的动作时,它回调用这个子程序,这就是Windows基于消息的体系的最基本的概念,换句话说,在Dos下,我们通过INT指令调用系统,而在Windows 下,有很多时候是你指定子程序地址让Windows来调用你。 invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0中的 offset _ProcDlgMain 就指定了如果有消息发生,Windows就来执行这个子程序,参数中的 DLG_MAIN 就是在资源文件中定义的对话框模板编号。 hInstance 是对话框所在的资源文件的句柄。
    另外,在_ProcDlgMain 子程序中,Windows传给我们4个参数hWnd,wMsg,wParam,lParam,其中,hWnd是对话框的窗口句柄,wMsg表示现在发生的消息事件,如这个对话框初始化时Windows会以WM_INITDIALOG为消息调用,关闭时为WM_CLOSE,按下对话框上的按钮时为WM_COMMAND等,wParam和lParam是附加的参数,对应不同的消息对应不同定义,具体可以参考Win32 Programmer's reference。



4.     编写一个简单的窗口

有关窗口的基本知识

    窗口是屏幕上的矩形区域。一个窗口可以从键盘或者鼠标接受用户的输入,并在其内部显示图形输出。一个应用程序窗口通常包含程序的标题条、菜单、边框,滚动条。其中,对话框也是一种窗口。不同的是,对话框表面通常包含几个其它窗口,称之为“子窗口”。这些子窗口的形式有压入按钮、单选按钮、复选框、文本输入区域、列表框和滚动条等。 用户将这些窗口看成屏幕上的对象,可以通过按下一个按钮或者滚动一个滚动条与这些对象直接交互。
    窗口以“消息”的形式接收窗口的输入,窗口也用消息与其它窗口通讯。比如在程序窗口的大小改变时,字处理器会重新格式化其中的文本。窗口大小改变的细节是由操作系统处理的,但程序能够响应这个系统功能。当用户改变窗口的大小时,Windows给程序发送一条消息指出新窗口的大小。然后,程序就可以调整窗口中的内容,以响应大小的变化。程序创建的每一个窗口都有相关的窗口过程。也就是给这个窗口指定一个子程序(窗口过程),Windows通过调用它来给窗口发送消息。窗口过程再根据此消息进行处理,然后将控制返回给Windows。
    窗口在“窗口类”的基础上创建的。Windows定义了确省的窗口过程,如果你对所有的消息都让Windows自己处理,那么你就能得到一个标准的窗口,同样,你也可以选择处理自己感兴趣的消息,这样,相当于产生了不同的子类,也就形成了不同的应用程序。同样,子窗口也是基于同一个窗口类,并且使用同一个窗口过程。例如,所有Windows 程序中的所有按钮都基于同一窗口类。这个窗口类有一个处理所有按钮消息的窗口过程,但是,如果你按自己的设想设计一个按钮,如想把按钮的表面换成位图,你就可以自己处理按钮窗口的 WM_PAINT 消息,当 Windows 需要画按钮表面的时候,你就可以随自己的意思去画。
    Windows程序开始执行后,Windows为该程序创建一个“消息队列”。这个消息队列用来存放该程序可能创建的各种不同窗口的消息。程序中有一段代码,叫做“消息循环”, 它用来从队列中取出消息,并且将它们发送给相应的窗口过程。在没有消息发生的时候,你的程序实际上就在消息循环中转圈子。
    创建一个窗口的过程如下: 取得程序的实例句柄(hInstance) 注册窗口类,实际上就是为你的窗口指定处理消息的过程,定义光标,窗口风格,颜色等参数 创建窗口 显示窗口 然后进入消息循环,也就是不停地检测有无消息,并把它发送给窗口进程去处理。

创建一个窗口的代码在不同的程序中实际上是几乎一模一样的,所以你编一个新的程序时可以把这一段拷来拷去,稍微修改一下就行,程序的大部分代码实际上是用在窗口过程中,因为这才是不同程序的不同之处。窗口过程的编程要点如下: 从Windows传给窗口过程的参数 uMsg 得到消息类型,并转到不同的分枝去处理。 对自己已经处理的消息,返回 Windows 时必须在eax 中返回0。 自己不处理的消息,必须调用 DefWindowProc 处理,并把返回值传回Windows,否则,Windows会无法显示。

uMsg 参数中指定的消息有280多种,实际上我们需要处理的只有重要的几种,如Windows在创建的时候会发送 WM_CREATE 消息,我们就可以在这时候初始化,分配内存等等,而退出时会发送 WM_CLOSE,我们就可以进行释放内存等清除工作,当Windows上的菜单或按钮被按下时发送 WM_COMMAND 消息等等,具体可以参考 Win32 Programmer's Reference。下面,我们来看一个创建窗口的简单程序。

一个创建窗口的程序

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Programmed by 罗云彬, [email protected] ; Website: http://asm.yeah.net ; LuoYunBin's Win32 ASM page (罗云彬的编程乐园) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat, stdcall option casemap :none ; case sensitive include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib gdi32.lib IDI_MAIN equ 1000 ;icon IDM_MAIN equ 4000 ;menu IDM_EXIT equ 4001 .data? hInstance dd ? hWinMain dd ? hMenu dd ? szBuffer db 256 dup (?) .data szClassName db "Windows Template",0 szCaptionMain db '窗口模板',0 .code start: call _WinMain invoke ExitProcess,NULL _WinMain proc local @stWcMain:WNDCLASSEX local @stMsg:MSG invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke LoadIcon,hInstance,IDI_MAIN mov hIcon,eax invoke LoadMenu,hInstance,IDM_MAIN mov hMenu,eax ;*************** 注册窗口类 ***************************************** invoke LoadCursor,0,IDC_ARROW mov @stWcMain.hCursor,eax mov @stWcMain.cbSize,sizeof WNDCLASSEX mov @stWcMain.hIconSm,0 mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW mov @stWcMain.lpfnWndProc,offset WndMainProc mov @stWcMain.cbClsExtra,0 mov @stWcMain.cbWndExtra,0 mov eax,hInstance mov @stWcMain.hInstance,eax mov @stWcMain.hIcon,0 mov @stWcMain.hbrBackground,COLOR_WINDOW + 1 mov @stWcMain.lpszClassName,offset szClassName mov @stWcMain.lpszMenuName,0 invoke RegisterClassEx,addr @stWcMain ;*************** 建立输出窗口 *************************************** invoke CreateWindowEx,WS_EX_CLIENTEDGE,\ offset szClassName,offset szCaptionMain,\ WS_OVERLAPPEDWINDOW OR WS_VSCROLL OR WS_HSCROLL,\ 0,0,550,300,\ NULL,hMenu,hInstance,NULL invoke ShowWindow,hWinMain,SW_SHOWNORMAL invoke UpdateWindow,hWinMain ;*************** 消息循环 ******************************************* .while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endw ret _WinMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> WndMainProc proc uses ebx edi esi, \ hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD mov eax,uMsg .if eax == WM_CREATE mov eax,hWnd mov hWinMain,eax call _Init ;******************************************************************** .elseif eax == WM_COMMAND .if lParam == 0 mov eax,wParam .if ax == IDM_EXIT call _Quit .endif .endif ;******************************************************************** .elseif eax == WM_CLOSE call _Quit ;******************************************************************** .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret WndMainProc endp _Init proc invoke SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon ret _Init endp ;******************************************************************** _Quit proc invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL ret _Quit endp ;******************************************************************** end start

窗口程序的分析

    让我们来简单分析一下这个程序,首先程序调用 _WinMain,在_WinMain 中定义了两个局部变量 @stMsg 和 @stWinMain,数据类型分别是 MSG 和 WNDCLASSEX结构,在参考手册中,可以看到WNDCLASSEX定义了一个窗口的所有参数,如使用的菜单、光标、颜色、窗口过程等,接下来的一大堆 mov 指令实际上就是在填写这个数据结构,填写完成后,最重要的两句是 mov @stWcMain.lpfnWndProc,offset WndMainProc 定义了处理消息的窗口过程, mov @stWcMain.lpszClassName,offset szClassName 定义了你要创建的类的名称,然后就是使用 RegisterClassEx 注册这个窗口类,注意,这时候窗口并没有创建,你只不过是定义好了一个子类,接下去你要用你定义的类去创建一个窗口。也就是使用 CreateWindowEx 函数去创建它。在手册中,CreateWindowEx 是这样定义的:

HWND CreateWindowEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data );

其中的参数 dwExStyle 是窗口的风格,lpClassName 就是我们自己定义的类的名字。如果大家要创建一个已经定义好的类,如 RichEdit 类等等,只要把 lpClassName 指向 "RichEdit32" 字符串就行了,当然这时就不用 RegisterClass 以及编写自己的窗口过程了。执行 CreateWindowEx 后,得到一个返回值就是窗口句柄,这个值在以后是要经常用到了,所以要先保存下来。这时窗口并没有在屏幕上显示出来,而是处于隐藏状态,我们要用 ShowWindow 来显示出窗口并用UpdateWindow 来绘窗口的内容。
    窗口显示出来后,程序就进入一个循环----消息循环,前面我已经说过,作用是不停地接收 Windows 消息并发送给窗口过程去处理。GetMessage 从消息队列中取出一条消息,如果取得的消息不是 WM_QUIT,那么 GetMessage 返回一个非零值,否则返回零,这时候循环结束,程序执行 ExitProcess退回操作系统。TranslateMessage 将消息进行一些键盘转换,用于处理一些快捷键,DispatchMessage 将处理后的消息发回 Windows,由Windows调用窗口进程进行处理,当窗口进程处理完返回后,程序才从 DispatchMessage 返回,从而开始下一个 GetMessage 调用。这些函的参数可以参考手册。

窗口过程的分析

    窗口过程有四个参数,hWnd 是本窗口的句柄,和创建窗口时返回的值相同,uMsg 是本次调用的消息类型,wParam 和lParam是消息的参数,其含义和数值根据消息的不同而不同。在本程序中,我们处理 WM_CREATE,WM_COMMAND 和 WM_QUIT 消息,然后返回0,对不处理的消息,使用 invoke DefWindowProc,hWnd,uMsg,wParam,lParam 来处理并直接用 ret 将返回值传回 Windows。在响应 WM_CLOSE 消息时,我们用 DestroyWindow 清除窗口并用PostQuitMessage 产生一条 WM_QUIT 消息,从而使程序在 消息循环调用GetMessage 时返回0,以结束消息循环并结束程序。



5.           菜单和加速键的使用

有关菜单和加速键

    菜单是Windows标准界面的最重要的组成部分,窗口的菜单条位于标题栏的下方,这个菜单通常被称为主菜单,列在主菜单下面的菜单项被称为下拉式菜单,或弹出式菜单、子菜单等,而在标题栏左边的图标上点击也会弹出一个菜单,叫做系统菜单。加速键实际上是菜单项的快捷键,应用程序常在菜单项的右边标出激活这个菜单项的快捷键,这就是加速键。菜单的结构是可嵌套的,也就是说,你可以在选择一个菜单项时弹出另一个菜单。菜单项的种类有正常的、被禁用的、灰化的、水平分隔线等。本节的示范程序演示了各种类型的菜单:你可以在主菜单中看到正常的和禁用的、灰化的菜单,可以用右键单击窗口的任一部分弹出一个“弹出式菜单”,也可以看到我在系统菜单中添加了几项新的内容。
    在编程的处理中,菜单是在资源文件中定义的(当然,你可以不用资源文件,而在程序中用AppendMenu一项一项的添加,但使用资源文件无疑是最简单的办法),然后在程序中用LoadMenu来获得菜单句柄再使用。在资源文件中定义菜单的语法如下:

菜单ID menu discardable BEGIN popup "主菜单项一" BEGIN menuitem "弹出式菜单项一", 命令ID [,OPTION] menuitem "弹出式菜单项二", 命令ID [,OPTION] menuitem separator menuitem "弹出式菜单项三", 命令ID [,OPTION] ... END popup "主菜单项二" BEGIN menuitem "弹出式菜单项一", 命令ID [,OPTION] menuitem "弹出式菜单项二", 命令ID [,OPTION] menuitem "弹出式菜单项三", 命令ID [,OPTION] ... popup "嵌套的菜单项" BEGIN menuitem "弹出式菜单项一", 命令ID [,OPTION] menuitem "弹出式菜单项二", 命令ID [,OPTION] menuitem "弹出式菜单项三", 命令ID [,OPTION] ... END END ... END

菜单ID就是我们在程序中用LoadMenu装入菜单用到的资源编号,menuitem separator 定义了分隔菜单项用的水平线,菜单项定义中的option是属性,如GRAYED是灰化的,INACTIVE是被禁用的等等。而加速键实际上就是定义了对应于各个菜单项的热键,定义方法如下:

加速键ID accelerators BEGIN VK_F1, 对应的菜单命令ID, VIRTKEY VK_F2, 对应的菜单命令ID, VIRTKEY ... "A", 对应的菜单命令ID, VIRTKEY,CONTROL "B", 对应的菜单命令ID, VIRTKEY,CONTROL END

其中,加速键ID是我们在程序中用LoadAccelerator装入加速键的资源编号,下面的每一项定义了一个键,VK_F1表示用F1,“A”表示键A,下面的VIRTKEY是必需的,再下面的CONTROL“或SHIFT、ALT”表示用CONTROL键组合,也就是说,如果你定义了:"C",IDM_COPY,VIRTKEY,CONTROL 而且在菜单定义中定义了 menuitem "拷贝",IDM_COPY,那么,你在程序中按下Ctrl-C实际上就是执行了菜单项“拷贝”。
    菜单和加速键的编程是很简单的,初始化的部分你需要做以下事情: 取得程序的实例句柄(hInstance) 用LoadMenu装入菜单,得到菜单句柄 用LoadAccelerator装入加速键,得到加速键句柄 注册窗口类 创建窗口时在参数中制定菜单句柄 显示窗口 然后进入消息循环,在消息循环中用TranslateAccelerator来进行加速键的检测(详见源程序)

当窗口显示后,当一个菜单项或一个加速键被按下时,Windows向窗口过程发送WM_COMMAND消息,而当一个系统菜单中的菜单项被按下时,Windows 向窗口过程发送WM_SYSCOMMAND,菜单项命令的ID就包括在wParam的低16位中,在一般的编程中,如果我们不对系统菜单消息进行处理,那么只需在WM_COMMAND消息的处理中建立一段 .if/.elseif/.elseif .../.endif的语句对各个菜单命令ID进行处理就行了。

使用菜单和加速键的源程序

.386 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Programmed by 罗云彬, [email protected] ; Website: http://asm.yeah.net ; LuoYunBin's Win32 ASM page (罗云彬的编程乐园) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .model flat, stdcall option casemap :none ; case sensitive ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include shell32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib shell32.lib includelib gdi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> IDI_MAIN equ 1000 ;icon IDA_MAIN equ 2000 ;Accelerator IDM_MAIN equ 4000 IDM_OPEN equ 4101 IDM_OPTION equ 4102 IDM_EXIT equ 4103 IDM_SETFONT equ 4201 IDM_SETCOLOR equ 4202 IDM_FIND equ 4203 IDM_FINDPREV equ 4204 IDM_FINDNEXT equ 4205 IDM_TOOLBAR equ 4206 IDM_TOOLBARTEXT equ 4207 IDM_INPUTBAR equ 4208 IDM_STATUSBAR equ 4209 IDM_HELP equ 4301 IDM_ABOUT equ 4302 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hIcon dd ? hInstance dd ? hWinMain dd ? hMenu dd ? hSubMenu dd ? szBuffer db 256 dup (?) dwFlag dd ? ;******************************************************************** ; 标志位定义 F_TOOLBAR equ 00000001b F_TOOLBARTEXT equ 00000010b F_INPUTBAR equ 00000100b F_STATUSBAR equ 00001000b ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data szClassName db "Menu Example",0 szCaptionMain db '菜单应用示例',0 szMenuHelp db "帮助主题(&H)",0 szMenuAbout db "关于本程序(&A)...",0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code include Debug.asm ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 程序开始 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: call _WinMain invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 主窗口程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _WinMain proc local @stWcMain:WNDCLASSEX local @stMsg:MSG local @hAccelerator invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke LoadIcon,hInstance,IDI_MAIN mov hIcon,eax invoke LoadMenu,hInstance,IDM_MAIN mov hMenu,eax ;*************** 注册窗口类 ***************************************** invoke LoadCursor,0,IDC_ARROW mov @stWcMain.hCursor,eax mov @stWcMain.cbSize,sizeof WNDCLASSEX mov @stWcMain.hIconSm,0 mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW mov @stWcMain.lpfnWndProc,offset WndMainProc mov @stWcMain.cbClsExtra,0 mov @stWcMain.cbWndExtra,0 mov eax,hInstance mov @stWcMain.hInstance,eax mov @stWcMain.hIcon,0 mov @stWcMain.hbrBackground,COLOR_WINDOW + 1 mov @stWcMain.lpszClassName,offset szClassName mov @stWcMain.lpszMenuName,0 invoke RegisterClassEx,addr @stWcMain ;*************** 建立输出窗口 *************************************** invoke CreateWindowEx,WS_EX_CLIENTEDGE,\ offset szClassName,offset szCaptionMain,\ WS_OVERLAPPEDWINDOW OR WS_VSCROLL OR WS_HSCROLL,\ 100,100,550,300,\ NULL,hMenu,hInstance,NULL invoke ShowWindow,hWinMain,SW_SHOWNORMAL invoke UpdateWindow,hWinMain ;*************** 消息循环 ******************************************* invoke LoadAccelerators,hInstance,IDA_MAIN mov @hAccelerator,eax .while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateAccelerator,hWinMain,@hAccelerator,addr @stMsg .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endif .endw ret _WinMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> WndMainProc proc uses ebx edi esi, \ hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD local @stPos:POINT mov eax,uMsg .if eax == WM_CREATE mov eax,hWnd mov hWinMain,eax call _Init ;******************************************************************** .elseif eax == WM_COMMAND .if lParam == 0 mov eax,wParam movzx eax,ax .if eax == IDM_EXIT call _Quit .elseif eax == IDM_TOOLBAR xor dwFlag,F_TOOLBAR call _MenuStatus .elseif eax == IDM_TOOLBARTEXT xor dwFlag,F_TOOLBARTEXT call _MenuStatus .elseif eax == IDM_INPUTBAR xor dwFlag,F_INPUTBAR call _MenuStatus .elseif eax == IDM_STATUSBAR xor dwFlag,F_STATUSBAR call _MenuStatus .else _Debug "菜单命令","命令ID",eax .endif .endif ;******************************************************************** .elseif eax == WM_SYSCOMMAND mov eax,wParam movzx eax,ax .if eax == IDM_HELP || eax == IDM_ABOUT _Debug "菜单命令","命令ID",eax .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif ;******************************************************************** ; 按下右键时弹出一个POPUP菜单 ;******************************************************************** .elseif eax == WM_RBUTTONDOWN invoke GetCursorPos,addr @stPos invoke TrackPopupMenu,hSubMenu,TPM_LEFTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL ;******************************************************************** .elseif eax == WM_CLOSE call _Quit ;******************************************************************** .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif ;******************************************************************** ; 注意:WndProc 处理 Windows 消息后,必须在 Eax 中返回 0 ; 但是由 DefWindowProc 处理后的返回值不能改变,否则窗口 ; 将无法显示! ;******************************************************************** xor eax,eax ret WndMainProc endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 主窗口控制子程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _Init proc local @hSysMenu invoke SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon ;******************************************************************** ; POPUP菜单要用到子菜单才能实现 ;******************************************************************** invoke GetSubMenu,hMenu,1 mov hSubMenu,eax call _MenuStatus ;******************************************************************** ; 在系统菜单中添加菜单项 ;******************************************************************** invoke GetSystemMenu,hWinMain,FALSE mov @hSysMenu,eax invoke AppendMenu,@hSysMenu,MF_SEPARATOR,0,NULL invoke AppendMenu,@hSysMenu,MF_STRING,IDM_HELP,offset szMenuHelp invoke AppendMenu,@hSysMenu,MF_STRING,IDM_ABOUT,offset szMenuAbout ret _Init endp ;******************************************************************** ; 根据标志位设置相应菜单项的状态 ;******************************************************************** _MenuStatus proc test dwFlag,F_INPUTBAR .if ZERO? invoke CheckMenuItem,hMenu,IDM_INPUTBAR,MF_UNCHECKED .else invoke CheckMenuItem,hMenu,IDM_INPUTBAR,MF_CHECKED .endif test dwFlag,F_TOOLBAR .if ZERO? invoke CheckMenuItem,hMenu,IDM_TOOLBAR,MF_UNCHECKED .else invoke CheckMenuItem,hMenu,IDM_TOOLBAR,MF_CHECKED .endif test dwFlag,F_TOOLBARTEXT .if ZERO? invoke CheckMenuItem,hMenu,IDM_TOOLBARTEXT,MF_UNCHECKED .else invoke CheckMenuItem,hMenu,IDM_TOOLBARTEXT,MF_CHECKED .endif test dwFlag,F_STATUSBAR .if ZERO? invoke CheckMenuItem,hMenu,IDM_STATUSBAR,MF_UNCHECKED .else invoke CheckMenuItem,hMenu,IDM_STATUSBAR,MF_CHECKED .endif ret _MenuStatus endp ;******************************************************************** _Quit proc invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL ret _Quit endp ;******************************************************************** end start

程序的分析

    让我们来简单分析一下这个程序,首先这个程序和上一节的最简单的窗口程序的不同之处就是消息循环,如下:

.while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateAccelerator,hWinMain,@hAccelerator,addr @stMsg .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endif .endw

在循环中的TranslateAccelerator用来确定存放在MSG结构中的消息是不是键盘消息,如果是,它查找句柄@hAccelerator对应的加速键表,如果找到了一个匹配项,那么它将用命令ID向窗口发送WM_COMMAND消息,同时返回非0值,这时候表示消息已经被处理,不用再调用下面的TranslateMessage 和 DispatchMessage 了,如果不是,那么它将返回0,消息循环继续。
    另外,要说明的是弹出式菜单,在程序中我们响应WM_RBUTTONDOWN消息对按下右键进行处理, 然后调用GetCursorPos取得当前鼠标坐标,然后使用TrackPopupMenu在鼠标位置上弹出一个菜单,但是在资源文件中,“弹出式菜单”是无法直接定义的,所以在初始化部分,我们使用GetSubMenu 取出弹出式子菜单的句柄供TrackPopupMenu使用。



6.         工具栏和状态栏的使用

有关工具栏和状态栏

    工具栏和状态栏也是Windows标准界面的组成部分,工具栏一般位于菜单栏的下方,上面是一些系统定义的或自己定义的图片,可以通俗地显示这个按钮的作用。状态栏一般位于窗口的最下方,用来显示程序运行中的一些信息。工具栏和状态栏是Windows系统的两个通用的控件,你可以通过两个专用的 API 或者利用 CreateWindowEx 再使用制定的已经由系统定义的类来创建它们。这两个 API 分别是 CreateToolbarEx 和 CreateStatusWindow。下面将一下它们的用法。
    CreateToolbarEx 的声明为:

HWND CreateToolbarEx( HWND hwnd, DWORD ws, UINT wID, int nBitmaps, HINSTANCE hBMInst, UINT wBMID, LPCTBBUTTON lpButtons, int iNumButtons, int dxButton, int dyButton, int dxBitmap, int dyBitmap, UINT uStructSize}

它的参数中 hwnd 是父窗口(也就是我们的主窗口)的句柄,ws 是工具栏的风格,可以由几项合成,如 WS_VISIBLE 是创建是可见,TBSTYLE_FLAT表示平面按钮,WS_BORDER表示有边线等,具体可以见手册。wID 是工具栏的标识,nBitmaps 是定义按钮的图片个数,因为如果你要使用自己的图片,所有图片是要放在同一行中的,然后就由你自己指定中间的个数, hBMInst 是包含已定义系统图片的资源句柄,在你不想自己画图,使用内定的标准图片时使用,一般包含这些图片的 hInstance 已经在 Widnows.inc 中定义为 HINST_COMMCTRL,同样 wBMID 一般是 IDB_STD_SMALL_COLOR,表示使用大图片还是小图片等等。接下来是定义按钮的数据结构,中间定义了各个按钮的命令号,图片号及其他属性,结构如下:

TBBUTTON STRUCT iBitmap DWORD ? idCommand DWORD ? fsState BYTE ? fsStyle BYTE ? bReserved BYTE 2 dup(?) dwData DWORD ? iString DWORD ? TBBUTTON ENDS

其中,每个结构定义一个按钮,数据结构中iBitmap 是图片ID,idCommand 是按钮的命令号,这个命令号当你按下按钮的时候会出现在 WM_COMMAND 消息的 wParam 中,你就可以知道哪个按钮被按下了。fsState 是按钮的初始状态,如 TBSTATE_PRESSED 是已经按下的,详细见手册,fsStyle 是风格。
    而建立状态栏的 CreateStatusWindow 的声明如下:

HWND CreateStatusWindow( LONG style, LPCTSTR lpszText, HWND hwndParent, UINT wID );

style 是状态栏的风格,lpszText 指向初始化是要显示在状态栏的文本,你可以指向 NULL。hwndParent 是父窗口的句柄。wID 是窗口 ID。

使用工具栏和状态栏的源程序

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 是否包括调试代码 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DEBUG = 1 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Programmed by 罗云彬, [email protected] ; Website: http://asm.yeah.net ; LuoYunBin's Win32 ASM page (罗云彬的编程乐园) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 版本信息 ; 工具栏和状态栏演示程序 Ver 1.0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat, stdcall option casemap :none ; case sensitive ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib gdi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> IDI_MAIN equ 1000 ;icon IDM_MAIN equ 4000 ;menu IDM_TOOLBAR equ 4001 IDM_STATUSBAR equ 4002 IDM_EXIT equ 4003 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hInstance dd ? hWinMain dd ? hMenu dd ? hIcon dd ? hToolbar dd ? hStatusbar dd ? szBuffer db 256 dup (?) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data szClassName db "Toolbar Template",0 szCaptionMain db '工具栏和状态栏演示程序 - 罗云彬',0 dwFlag dd F_TOOLBAR or F_STATUSBAR ;******************************************************************** ; 标志位定义 F_TOOLBAR equ 00000001b F_STATUSBAR equ 00000010b stToolbar TBBUTTON < STD_FILENEW,1,TBSTATE_ENABLED,TBSTYLE_BUTTON,2 dup(0),-1> TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,2 dup(0),-1> TBBUTTON < STD_FILEOPEN,2,TBSTATE_ENABLED,TBSTYLE_BUTTON,2 dup(0),-1> TBBUTTON < STD_FILESAVE,3,TBSTATE_ENABLED,TBSTYLE_BUTTON,2 dup(0),-1> TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,2 dup(0),-1> TBBUTTON < STD_PRINT,4,TBSTATE_ENABLED,TBSTYLE_BUTTON,2 dup(0),-1> TBBUTTON < STD_PRINTPRE,0,TBSTATE_ENABLED,TBSTYLE_SEP,2 dup(0),-1> NUM_BUTTONS EQU 7 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code if DEBUG include Debug.asm endif ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 程序开始 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: call _WinMain invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 主窗口程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _WinMain proc local @stWcMain:WNDCLASSEX local @stMsg:MSG ;******************************************************************** ; 如果已经在运行,则激活已运行的进程 ;******************************************************************** invoke FindWindow,offset szClassName,NULL .if eax != NULL invoke ShowWindow,eax,SW_SHOWNORMAL invoke ExitProcess,NULL .endif invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke LoadIcon,hInstance,IDI_MAIN mov hIcon,eax invoke LoadMenu,hInstance,IDM_MAIN mov hMenu,eax ;*************** 注册窗口类 ***************************************** invoke LoadCursor,0,IDC_ARROW mov @stWcMain.hCursor,eax mov @stWcMain.cbSize,sizeof WNDCLASSEX mov @stWcMain.hIconSm,0 mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW mov @stWcMain.lpfnWndProc,offset WndMainProc mov @stWcMain.cbClsExtra,0 mov @stWcMain.cbWndExtra,0 mov eax,hInstance mov @stWcMain.hInstance,eax mov @stWcMain.hIcon,0 mov @stWcMain.hbrBackground,COLOR_BTNFACE+1 mov @stWcMain.lpszClassName,offset szClassName mov @stWcMain.lpszMenuName,0 invoke RegisterClassEx,addr @stWcMain ;*************** 建立输出窗口 *************************************** invoke CreateWindowEx,NULL,\ ;WS_EX_CLIENTEDGE,\ offset szClassName,offset szCaptionMain,\ WS_OVERLAPPEDWINDOW,\ ; OR WS_VSCROLL OR WS_HSCROLL,\ 50,50,550,350,\ NULL,hMenu,hInstance,NULL invoke ShowWindow,hWinMain,SW_SHOWNORMAL invoke UpdateWindow,hWinMain ;*************** 消息循环 ******************************************* .while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endw ret _WinMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> WndMainProc proc uses ebx edi esi, \ hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD mov eax,uMsg .if eax == WM_CREATE mov eax,hWnd mov hWinMain,eax call _Init ;******************************************************************** .elseif eax == WM_SIZE invoke SendMessage,hStatusbar,uMsg,wParam,lParam invoke SendMessage,hToolbar,uMsg,wParam,lParam ;******************************************************************** .elseif eax == WM_COMMAND mov eax,wParam movzx eax,ax .if eax == IDM_EXIT call _Quit .elseif eax == IDM_TOOLBAR xor dwFlag,F_TOOLBAR call _ArrangeWindow .elseif eax == IDM_STATUSBAR xor dwFlag,F_STATUSBAR call _ArrangeWindow .else _Debug "菜单和工具栏命令","命令ID",eax .endif ;******************************************************************** .elseif eax == WM_CLOSE call _Quit ;******************************************************************** .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif ;******************************************************************** ; 注意:WndProc 处理 Windows 消息后,必须在 Eax 中返回 0 ; 但是由 DefWindowProc 处理后的返回值不能改变,否则窗口 ; 将无法显示! ;******************************************************************** xor eax,eax ret WndMainProc endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 主窗口控制子程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _Init proc invoke SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon invoke CreateToolbarEx,hWinMain,\ WS_VISIBLE or WS_CHILD or TBSTYLE_FLAT or WS_BORDER,\ 1,0,HINST_COMMCTRL,IDB_STD_SMALL_COLOR,offset stToolbar,\ NUM_BUTTONS,0,0,0,0,sizeof TBBUTTON mov hToolbar,eax invoke CreateStatusWindow,WS_CHILD or WS_VISIBLE,NULL,hWinMain,2 mov hStatusbar,eax call _ArrangeWindow ret _Init endp ;******************************************************************** _Quit proc invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL ret _Quit endp ;******************************************************************** _ArrangeWindow proc local @stRect:RECT local @stRectTemp:RECT local @dwWidth:DWORD test dwFlag,F_TOOLBAR .if ZERO? invoke ShowWindow,hToolbar,SW_HIDE invoke CheckMenuItem,hMenu,IDM_TOOLBAR,MF_UNCHECKED .else invoke ShowWindow,hToolbar,SW_SHOW invoke CheckMenuItem,hMenu,IDM_TOOLBAR,MF_CHECKED .endif test dwFlag,F_STATUSBAR .if ZERO? invoke ShowWindow,hStatusbar,SW_HIDE invoke CheckMenuItem,hMenu,IDM_STATUSBAR,MF_UNCHECKED .else invoke ShowWindow,hStatusbar,SW_SHOW invoke CheckMenuItem,hMenu,IDM_STATUSBAR,MF_CHECKED .endif ret _ArrangeWindow endp ;******************************************************************** end start

程序的分析和要点

    在工具栏和状态栏编程中,要注意的就是工具栏和状态栏并不会随父窗口的大小变化自己调整位置和大小,所以要在父窗口的 WM_SIZE 消息中来移动和调整它们,这可以简单的把 WM_SIZE 消息传给它们就行了。不必自己再去计算。

.elseif eax == WM_SIZE invoke SendMessage,hStatusbar,uMsg,wParam,lParam invoke SendMessage,hToolbar,uMsg,wParam,lParam

另外,工具栏和状态栏也是一种子窗口,所以如果想把它们隐藏或显示的话,可以用标准的 ShowWindow 来处理。



7.         控件的子类化

有关控件子类化

    说到类,大家可能马上就想到C++,的确,类首先是在C中提出的,但是,这个概念在 Win32Asm 中仍然适用,因为在类的思路是这样的:先假设某个对象有不同的属性,当一个新的对象的某个属性和上面所说的对象有些不同,而别的属性一模一样,那么实际上除了处理这个属性的代码有些不同外,别的代码完全可以使用前面的对象的代码。在具体的应用中,我举个例子,比如说我们定义一个 "edit" 控件,那么这个控件的行为是由 Windows 内定的,因为它的窗口过程是在 Windows 系统内部的,但假如我们想编一个有语法检查的 "edit" 控件,是否我们除了语法检查的代码以外,还要编写很多代码来实现老的 "edit" 控件一模一样的功能呢?答案当然是否定的,实际上,我们可以截获一个标准 "edit"控件的 WM_CHAR 消息,检查键入的键并做处理,别的消息可以传给原来的窗口过程。示意如下:

在子类化之前: Windows => edit 控件的窗口过程

在子类化之后: Windows => 我们的过程代码 => edit 控件的窗口过程

在Windows 的 API 中有个函数可以用来实现这个功能,那就是 SetWindowLong PROTO hWnd,nIndex,dwNewLong ,参数的意思是 hWnd 是你要改变的窗口句柄,nIndex 是我们要改变窗口的什么属性,它的值可以是 GWL_EXSTYLE:改变窗口风格,GWL_WNDPROC:设置窗口的新的过程,这正是我们感兴趣的,还有是 GWL_USERDATA 这是窗口自定义的一个32位的数据。dwNewLong 是新的值,还有一个 API 是用来调用原来的窗口过程的,叫 CallWindwoProc PROTO lpPrevWndFunc,hWnd,Msg,wParam,lParam。
我们在使用时有下面的过程: 用 SetWindowLong,hWnd,GWL_WNDPROC,addr _NewProcAddress 设置我们自己的代码的地址,API 返回原来的过程地址 用 SetWindowLong,hWnd,GWL_USERDATA,eax 把原来的过程地址保存在自定义数据中。 这样,所有消息会先送到我们的过程中,然后在我们自己的过程中: 对要处理的消息进行处理,如果不希望原来的过程再处理,那么返回。 对自己不处理的消息,调用原来的窗口过程处理,并把返回值返回。方法是: 用 invoke GetWindowLong,hWnd,GWL_USERDATA 取出自定义数据中保存的原过程地址 用 invoke CallWindowProc,eax,hWnd,uMsg,wParam,lParam 调用原过程 UINT uStructSize}

本节教程提供了一个源程序,它是实现对话框中的文本的 URL 连接过程,我们看到有的程序中的文本是蓝色的,有下划线,然后鼠标移动到上面会变手型,就象浏览器中的超联结一样,而且按下会自动连接到网站上,仔细想想,我们并没有一个标准的控件或 API 来实现这样一个功能,因为这首先是一个文本,所以我们可以对这个文本进行子类化,处理它的WM_LBUTTONUP 消息来实现按下自动连上网站的功能;处理 WM_SETCURSOR 消息来让鼠标移到上面改变光标,具体源程序如下:

源程序 - 资源文件

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ;************************************************ #include ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Icon 1000 开始 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #define IDI_MAIN 1000 #define IDC_HANDLE 2000 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 对话框 3000 开始 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #define DLG_ABOUT 3000 #define ID_EMAIL 3001 #define ID_HOMEPAGE 3002 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 资源定义开始 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> IDI_MAIN ICON "Main.ico" IDC_HANDLE CURSOR "Handle.cur" ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DLG_ABOUT DIALOG DISCARDABLE 50, 50, 160, 30 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "URL 联结文本演示 - by 罗云彬" FONT 9, "宋体" BEGIN LTEXT "我的主页: ",-1, 5,5,54,9 LTEXT "http://asm.yeah.net", ID_HOMEPAGE, 55,5,80,9 LTEXT "我的E-mail: ", -1, 5,17,54,9 LTEXT "[email protected]",ID_EMAIL, 55,17,95,9 END

源程序 - 汇编源文件

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 是否包括调试代码 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DEBUG = 0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Programmed by 罗云彬, [email protected] ; Website: http://asm.yeah.net ; LuoYunBin's Win32 ASM page (罗云彬的编程乐园) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 版本信息 ; 窗口子类化演示程序 Ver 1.0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat, stdcall option casemap :none ; case sensitive ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include shell32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib shell32.lib includelib gdi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> IDI_MAIN equ 1000 ;icon IDC_HANDLE equ 2000 ;handle cursor DLG_ABOUT equ 3000 ;dialog - about ID_EMAIL equ 3001 ID_HOMEPAGE equ 3002 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hInstance dd ? hIcon dd ? szBuffer db 256 dup (?) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _HyperLinkProc proto :DWORD,:DWORD,:DWORD,:DWORD ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data hCursorHandle dd ? szHomePage db "http://asm.yeah.net",0 szEmail db "mailto:[email protected]" db "?subject=嗨!我喜欢你的程序!",0 .code if DEBUG include Debug.asm endif ;******************************************************************** ; 关于对话框中超级连接的窗口程序 ;******************************************************************** _HyperLinkProc proc hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD mov eax,uMsg .if eax == WM_LBUTTONUP invoke GetDlgCtrlID,hWnd .if eax == ID_HOMEPAGE invoke ShellExecute,0,0,offset szHomePage,0,0,0 .elseif eax == ID_EMAIL invoke ShellExecute,0,0,offset szEmail,0,0,0 .endif .elseif eax == WM_NCHITTEST ;将 WM_NCHITTEST 返回 TRUE 可以接收鼠标动作,实现按下功能 ! mov eax,TRUE ret .elseif eax == WM_SETCURSOR invoke SetCursor,hCursorHandle .else invoke GetWindowLong,hWnd,GWL_USERDATA invoke CallWindowProc,eax,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret _HyperLinkProc endp ;******************************************************************** ; 对话框窗口主程序 ;******************************************************************** AboutDialogProc proc uses ebx edi esi, \ hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD local @stWindow:RECT local @dwWidth:DWORD,@dwHeight:DWORD local @hWinTemp:DWORD local @stFont:LOGFONT,@hFontOutput:DWORD mov eax,uMsg .if eax == WM_CLOSE invoke EndDialog,hWnd,NULL .elseif eax == WM_INITDIALOG invoke GetModuleHandle,NULL invoke LoadCursor,eax,IDC_HANDLE mov hCursorHandle,eax invoke GetDlgItem,hWnd,ID_HOMEPAGE mov @hWinTemp,eax invoke SetWindowLong,@hWinTemp,GWL_WNDPROC,addr _HyperLinkProc invoke SetWindowLong,@hWinTemp,GWL_USERDATA,eax invoke GetDlgItem,hWnd,ID_EMAIL mov @hWinTemp,eax invoke SetWindowLong,@hWinTemp,GWL_WNDPROC,addr _HyperLinkProc invoke SetWindowLong,@hWinTemp,GWL_USERDATA,eax .elseif eax == WM_CTLCOLORSTATIC invoke GetDlgCtrlID,lParam .if eax == ID_HOMEPAGE || eax == ID_EMAIL invoke SendMessage,lParam,WM_GETFONT,0,0 mov @hFontOutput,eax invoke GetObject,@hFontOutput,sizeof LOGFONT,addr @stFont mov @stFont.lfUnderline,TRUE invoke CreateFontIndirect,addr @stFont mov @hFontOutput,eax invoke SelectObject,wParam,eax invoke SetTextColor,wParam,Blue invoke GetSysColor,COLOR_MENU invoke SetBkColor,wParam,eax invoke DeleteObject,@hFontOutput ;******************************************************************** ; 注意此处一定要把StockOject的返回值返回,否则无法显示颜色 ;******************************************************************** invoke GetStockObject,HOLLOW_BRUSH .else mov eax,FALSE ret .endif ret .else ;******************************************************************** ; 注意:对话框的消息处理后,要返回 TRUE,对没有处理的消息 ; 要返回 FALSE ;******************************************************************** mov eax,FALSE ret .endif mov eax,TRUE ret AboutDialogProc endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 程序开始 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,DLG_ABOUT,\ NULL,offset AboutDialogProc,DLG_ABOUT invoke ExitProcess,NULL ;******************************************************************** end start

程序的分析和要点

    在资源中,我们定义了两个文本框,ID 分别为 ID_HOMEPAGE 和 ID_EMAIL,在主对话框的过程的 initdialog 消息中,我们用 GetDlgItem 取的它们的 hWnd,然后进行子类化,我们把新的过程设置到了 _HyperLinkProc 中

invoke GetDlgItem,hWnd,ID_HOMEPAGE mov @hWinTemp,eax invoke SetWindowLong,@hWinTemp,GWL_WNDPROC,addr _HyperLinkProc invoke SetWindowLong,@hWinTemp,GWL_USERDATA,eax invoke GetDlgItem,hWnd,ID_EMAIL mov @hWinTemp,eax invoke SetWindowLong,@hWinTemp,GWL_WNDPROC,addr _HyperLinkProc invoke SetWindowLong,@hWinTemp,GWL_USERDATA,eax

然后在新的处理过程中,检测到 WM_LBUTTONUP 消息(鼠标左键放开)就使用 ShellExecute API 来连到网站,检测 WM_NCHITTEST 来使文本控件接收鼠标的消息,检测 WM_SETCURSOR 消息把光标设置成手形,对这些消息以外的消息我们是不处理的,那就用 CallWindowProc 来调用原来的过程进行处理。

.if eax == WM_LBUTTONUP invoke GetDlgCtrlID,hWnd .if eax == ID_HOMEPAGE invoke ShellExecute,0,0,offset szHomePage,0,0,0 .elseif eax == ID_EMAIL invoke ShellExecute,0,0,offset szEmail,0,0,0 .endif .elseif eax == WM_NCHITTEST ;将 WM_NCHITTEST 返回 TRUE 可以接收鼠标动作,实现按下功能 ! mov eax,TRUE ret .elseif eax == WM_SETCURSOR invoke SetCursor,hCursorHandle .else invoke GetWindowLong,hWnd,GWL_USERDATA invoke CallWindowProc,eax,hWnd,uMsg,wParam,lParam ret .endif

本文地址:http://com.8s8s.com/it/it24280.htm