end start
option casemap:none 一句的意思是告诉 masm 要区分标号的大小写,譬如:start 和 start 是不同的。请注意新的伪指令 include,跟在其后的文件名所指定的文件在编译时将“插”在该处。在我们上面的程序段中,当masm处理到语句 include \masm\include\windows.inc 时,它就会打开文件夹\masm32\include 中的文件windows.inc,这和您把整个文件都粘贴到您的源程序中的效果是一样的。 hutch 的 windows.inc 包含了 win32 编程所需要的常量和结构体的定义。 但是它不包含函数原型的定义。尽管 hutch 和我尽力包含所有的常量和结构体的定义,但仍会有不少遗漏,为此我们将不断加入新的内容。请随时注意我们主页,下载最新的头文件。
您的应用程序除了从 windows.inc 中得到相关变量结构体的定义外,还需要从其他的头文件中得到函数原型的声明,这些头文件都放在 \masm32\include 文件夹中。 在我们上面的例子中调用了驻扎在 kernel.dll 中的函数,所以需要包含有这个函数原型声明的头文件 kernel.inc。如果用文本编辑器打开该文件您会发现里面全是从 kernel.dll中引出的函数的声明。如果您不包含kernel.inc,您仍然可以调用(call)exitprocess,但不能够调用(invoke)exitprocess(这会无法通过编译器和连接器的参数合法性检查)。所以若用 invoke 去调用一个函数,您就必须事先声明,当然不一定要包含我们的头文件,您完全可以在调用该函数前在源代码的适当位置进行声名。包含头文件主要是为了节省时间(译者:当然还有正确性)
接下来我们来看看 includelib 伪指令,和 include 不同,它仅仅是告诉编译器您的程序引用了哪个库。当编译器处理到该指令时会在生成的目标文件中插入链接命令告诉链接器链入什么库。当然您还可以通过在链接器的命令行指定引入库名称的方法来达到和用includelib指令相同的目的,但考虑到命令行仅能够传递128个字符而且要不厌其烦地在命令行敲字符,所以这种方法是非常不可取的。
好了,现在保存例子,取名为msgbox.asm。把 ml.exe 的路径放到 path 环境变量中,键入下面一行 进行编译:
ml /c /coff /cp msgbox。asm (译者注:命令行参数大小写是有区别的)
- /c 是告诉masm只编译不链接。这主要是考虑到在链接前您可能还有其他工作要做。
- /coff 告诉masm产生的目标文件用 coff 格式。masm 的 coff 格式是coff(common object file format:通用目标文件格式) 格式的一种变体。在 unix 下的 coff 格式又有不同。
- /cp 告诉 masm 不要更改用户定义的标识符的大小写。若您用的是 hutch 的包含文件的话,在.model 指令下加入 "option casemap:none" 语句,可达到同样的效果。
当您成功的编译了 msgbox.asm 后,编译器会产生 msgbox.obj 目标文件,目标文件和可执行文件只一步之遥,目标文件中包含了以二进制形式存在的指令和数据,比可执行文件相差的只是链接器加入的重定位信息。
好,我们来链接目标文件:
link /subsystem:windows /libpath:c:\masm32\lib msgbox.obj
- /subsystem:windows 告诉链接器可执行文件的运行平台
- /libpath:〈path to import library〉 告诉链接器引入库的路径。
链接器做的工作就是根据引入库往目标文件中加入重定位信息,最后产生可执行文件。 既然得到了可执行文件,我们来运行一下。好,一、二、三,go!屏幕上什么都没有。哦,对了,我们除了调用了 exitprocess 函数外,甚麽都还没做呢!但是别一点成就感都没有哦,因为我们用汇编所写的是一个真正 windows 程序,不信的话,查查您磁盘上的 msgbox.exe文件,在我的机器上它的大小足有1,536字节呢。
下面我们来做一点可以看的见摸的着的,我们在程序中加入一个对话框。该函数的原型如下:
messagebox proto hwnd:dword, lptext:dword, lpcaption:dword, utype:dword
- hwnd 是父窗口的句柄。句柄代表您引用的窗口的一个地址指针。它的值对您编 windows 程序并不重要(译者注:如果您想成为高手则是必须的),您只要知道它代表一个窗口。当您要对窗口做任何操作时,必须要引用该窗口的指针。
- lptext 是指向您要显示的文本的指针。指向文本串的指针事实上就是文本串的首地址。
- lpcaption 是指向您要显示的对话框的标题文本串指针。
- utype 是显示在对话框窗口上的小图标的类型。
下面是源程序
.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
.data
msgboxcaption db "iczelion tutorial no.2",0
msgboxtext db "win32 assembly is great!",0
.code
start:
invoke messagebox, null, addr msgboxtext, addr msgboxcaption, mb_ok
invoke exitprocess, null
end start
编译、链接上面的程序段,得到可执行文件。运行,哈哈,窗口上弹出了一个对话框,上面有一行字:“win32 assembly is great!”。想一想,我们是用汇编写出来的,所以我们有理由为编写了一个最简单的 win32 程序感到高兴。(译者注:如果明天我们能够像在 dos 下那样每一行都用汇编写,那我们有理由为自己感到自豪。)
好,我们回过头来看看上面的源代码。我们在.data“分段”定义了两个null结尾的字符串。我们用了两个常量:null 和 mb_ok。这些常量在windows.inc 文件中有定义,使用常量使得您的程序有较好的可读性。 addr 操作符用来把标号的地址传递给被调用的函数,它只能用在 invoke 语句中,譬如您不能用它来把标号的地址赋给寄存器或变量,如果想这样做则要用 offset 操作符。在 offset 和 addr 之间有如下区别:
- addr不可以处理向前引用,offset则能。所谓向前引用是指:标号的定义是在invoke 语句之后,譬如在如下的例子:
invoke messagebox,null, addr msgboxtext,addr msgboxcaption,mb_ok
......
msgboxcaption db "iczelion tutorial no.2",0
msgboxtext db "win32 assembly is great!",0
如果您是用 addr 而不是 offset 的话,那 masm 就会报错。 - addr可以处理局部变量而 offset 则不能。局部变量只是在运行时在堆栈中分配内存空间。而 offset 则是在编译时由编译器解释,这显然不能用offset 在运行时来分配内存空间。编译器对 addr 的处理是先检查处理的是全局还是局部变量,若是全局变量则把其地址放到目标文件中,这一点和 offset 相同,若是局部变量,就在执行 invoke 语句前产生如下指令序列:
lea eax, localvar
push eax