📄 003.txt
字号:
MessageBox Proto :dword,:dword,:dword,:dword
在Win32环境中,和字符串相关的API共有两类,分别对应两个字符集:一类是处理 ANSI 字符集的,另一类是处理 Unicode 字符集的。前一类函数名字的尾部带一个“A”字符,处理Unicode的则带一个“W”字符。我们比较熟悉的ANSI字符串是以 NULL 结尾的一串字符数组,每一个ANSI字符占一个字节宽。对于欧洲语言体系,ANSI 字符集已足够了,但对于有成千上万个不同字符的几种东方语言体系来说,Unicode 字符集更有用。每一个Unicode字符占两个字节的宽度,这样一来就可以在一个字符串中使用65 336个不同的字符了。
MessageBox和显示字符串有关,同样它有两个版本,严格地说,系统中有两个定义:
MessageBoxA Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword
MessageBoxW Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword
虽然《Microsoft Win32 Programmer~s Reference》中只有一个MessageBox定义,但User32.dll中确确实实没有MessageBox,而只有MessageBoxA和MessageBoxW,那么为什么还是可以使用MessageBox呢?实际上在程序的头文件user32.inc中有一句:
MessageBox equ
它把MessageBox偷梁换柱变成了MessageBoxA。在源程序中继续沿用MessageBox是 为了程序的可读性以及保持和手册的一致性,但对于编译器来说,实际是在使用MessageBoxA。
由于并不是每个Win32系统都支持W系列的API,在Windows 9x系列中,对Unicode是不支持的,很多的API只有ANSI版本,只有Windows NT系列才对Unicode完全支持。为了编写在几个平台中通用的程序,一般应用程序都使用ANSI版本的API函数集。
为了使程序更有移植性,在源程序中一般不直接指明使用Unicode还是ANSI版本,而是使用宏汇编中的条件汇编功能来统一替换,如在源程序中使用Messagebox,但在头文件中定义:
if UNICODE
MessageBox equ
else
MessageBox equ
endif
所有涉及版本问题的API都可以按此方法定义,然后在源程序的头部指定UNICODE=1或UNICODE=0,重新编译后就能产生不同的版本。
4.include语句
对于所有要用到的API函数,在程序的开始部分都必须预先声明,但这一个步骤显然是比较麻烦的,为了简化操作,可以采用各种语言通用的解决办法,就是把所有的声明预先放在一个文件中,在用到的时候再用include语句包含进来。现在回到Win32 Hello World程序,这个程序用到了两个API函数:MessageBox和ExitProcess,它们分别在User32.dll和Kernel32.dll中,在MASM32工具包中已经包括了所有DLL的API函数声明列表,每个DLL对应名.inc>文件,在源程序中只要使用include语句包含进来就可以了:
include user32.inc
include kernel32.inc
当用到其他的API函数时,只需相应增加对应的include语句。
include语句还用来在源程序中包含别的文件,当多个源程序用到相同的函数定义、常量定义,甚至源代码时,可以把相同的部分写成一个文件,然后在不同的源程序中用include语句包含进来。
编译器对include语句的处理仅是简单地把这一行用指定的文件内容替换掉而已。
include语句的语法是:
include 文件名
或 include <文件名>
当遇到要包括的文件名和MASM的关键字同名等可能会引起编译器混淆的情况时,可以用<>将文件名括起来。
5. includelib语句
在DOS汇编中,使用中断调用系统功能是不必声明的,处理器自己知道到中断向量表中去取中断地址。在Win32汇编中使用API函数,程序必须知道调用的API函数存在于哪个DLL中,否则,操作系统必须搜索系统中存在的所有DLL,并且无法处理不同DLL中的同名函数,这显然是不现实的,所以,必须有个文件包括DLL库正确的定位信息,这个任务是由导入库来实现的。
在使用外部函数的时候,DOS下有函数库的概念,那时的函数库实际上是静态库,静态库是一组已经编写好的代码模块,在程序中可以自由引用,在源程序编译成目标文件,最后要链接成可执行文件的时候,由link程序从库中找出相应的函数代码,一起链接到最后的可执行文件中。DOS下C语言的函数库就是典型的静态库。库的出现为程序员节省了大量的开发时间,缺点就是每个可执行文件中都包括了要用到的相同函数的代码,占用了大量的磁盘空间,在执行的时候,这些代码同样重复占用了宝贵的内存。
Win32环境中,程序链接的时候仍然要使用函数库来定位函数信息,只不过由于函数代码放在DLL文件中,库文件中只留有函数的定位信息和参数数目等简单信息,这种库文件叫做导入库,一个DLL文件对应一个导入库,如User32.dll文件用于编程的导入库是User32.lib,MASM32工具包中包含了所有DLL的导入库。
为了告诉链接程序使用哪个导入库,使用的语句是:
includelib 库文件名
或 includelib <库文件名>
和include的用法一样,在要包括让编译器混淆的文件名时加方括号。Win32 Hello world程序用到的两个API函数MessageBox和ExitProcess分别在User32.dll和Kernel32.dll中,那么在源程序使用的相应语句为:
includelib user32.lib
includelib kernel32.lib
和include语句的处理不同,includelib不会把 .lib文件插入到源程序中,它只是告诉链接器在链接的时候到指定的库文件中去找而已。
3.2.3 API参数中的等值定义
再回过头来看显示消息框的语句:
invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK
在uType这个参数中使用了MB_OK,这个MB_OK是什么意思呢,先来看《Microsoft Win32 Programmer~s Reference》中的说明:
uType —— 定义对话框的类型,这个参数可以是以下标志的合集:
要定义消息框上显示按钮,用下面的某一个标志:
MB_ABORTRETRYIGNORE —— 消息框有三个按钮:“终止”,“重试”和“忽略”
MB_HELP —— 消息框上显示一个“帮助”按钮,按下后发送WM_HELP消息
MB_OK —— 消息框上显示一个“确定”按钮,这是默认值
MB_OKCANCEL —— 消息框上显示两个按钮:“确定”和“取消”
MB_RETRYCANCEL —— 消息框上显示两个按钮:“重试”和“忽略”
MB_YESNO —— 消息框上显示两个按钮:“是”和“否”
MB_YESNOCANCEL —— 消息框上显示三个按钮:“是”、“否”和“取消”
要在消息框中显示图标,用下面的某一个标志:
MB_ICONWARNING —— 显示惊叹号图标
MB_ICONINFORMATION —— 显示消息图标
MB_ICONASTERISK —— 显示危险图标
MB_ICONQUESTION —— 显示问号图标
MB_ICONSTOP —— 显示停止图标
……
这些是uType参数说明中的一小半,可以看出,参数中可以用的值有很多种,让我们换一个值试试看,把语句改为:
invoke MessageBox,NULL,offset szText,offset szCaption, MB_ICONWARNING or MB_YESNO
再编译执行看,屏幕上出现了一个不一样的消息框,如图3.3所示。
和参数说明中的一样!消息框中出现了一个惊叹号图标,按钮也变成了“是”和“否”两个按钮!MB_ICONWARNING和MB_YESNO等参数究竟是什么意思呢,MASM中显然没有这样的预定义,让我们先来找Visual C++的头文件,在WinUser.h中可以找到下面一段:
/*
* MessageBox() Flags
*/
#define MB_OK Ox00000000L
#define MB_OKCANCEL Ox00000001L
#define MB_ABORTRETRYIGNORE Ox00000002L
#define MB_YESNOCANCEL Ox00000003L
#define MB_YESNO Ox00000004L
#define MB_RETRYCANCEL Ox00000005L
#define MB_ICONHAND Ox00000010L
#define MB_ICONQUESTION Ox00000020L
#define MB_ICONEXCLAMATION Ox00000030L
#define MB_ICONASTERISK Ox00000040L
#if(WINVER >= Ox0400)
#define MB_USERICON Ox00000080L
#define MB_ICONWARNING MB_ICONEXCLAMATION
#define MB_ICONERROR MB_ICONHAND
#endif /* WINVER >= 0x0400 */
#define MB_ICONINFORMATION MB_ICONASTERISK
#define MB_ICONSTOP MB_ICONHAND
……
显然,MB_YESNO就是4,MB_ICONWARNING就是30h,默认的MB_OK就是0,Win32 API的参数使用这样的定义方法显然是为了免除程序员死记数值定义的麻烦。在编写Win32汇编程序的时候,MASM32工具包中的Windows.inc也包括了所有这些参数的定义,只要在程序的开头包含这个定义文件:
include windows.inc
就可以方便地完全按照API手册来使用Win32函数。
打开\masm32\include目录下的Windows.inc查看一下,可以发现整个文件总共有两万六千多行,包括了几乎所有的Win32 API参数中的常量和数据结构定义。正是有了这个文件中详尽的定义,Win32ASM才得以流行起来,试想一下,哪个程序员愿意每使用一个API语句,就到函数手册中去看参数,然后到Microsoft发布的Visual C++的头文件中去找对应的数值,再应用到汇编源程序中?这样会有80%以上的时间花在做无用功上(最后还是要骂Microsoft为什么不提供汇编格式的头文件,毕竟MASM32工具包不是Microsoft出的)。
有时候由于版本的原因,当使用最新的API手册时,会发现有些参数使用的常量在Windows.inc中并没有定义,这下惨了,谁都不知道类似于MB_XXXYYY的东西代表什么数值,Microsoft的《Microsoft Programmer~s Reference》手册中从来就不会把参数对应的数值写进去。遇到这种情况,只有拿出最原始的办法了,就是到最新的Visual C++或SDK的include目录中去,在C语言格式的 .h头文件中把定义找出来,然后自行增补到Windows.inc中去。如果这样也找不到定义值的话,那只好放弃使用这个API了。
3.3 标号、变量和数据结构
当程序中要跳转到另一位置时,需要有一个标识来指示新的位置,这就是标号,通过在目的地址的前面放上一个标号,可以在指令中使用标号来代替直接使用地址。
使用变量是任何编程语言都要遇到的工作,Win32汇编也不例外,在MASM中使用变量也有需要注意的几个问题,错误地使用变量定义或用错误的方法初始化变量会带来难以定位的错误。变量是计算机内存中已命名的存储位置,在C语言中有很多种类的变量,如整数型、浮点型和字符串等,不同的变量有不同的用途和尺寸,比如说虽然长整数和单精度浮点数都是32位长,但它们的用途不同。
顾名思义,变量的值在程序运行中是需要改变的,所以它必须定义在可写的段内,如 .data和 .data?,或者在堆栈内。按照定义的位置不同,MASM中的变量也分为全局变量和局部变量两种。
在MASM中标号和变量的命名规范是相同的,它们是:
1. 可以用字母、数字、下划线及符号@、$和?。
2. 第一个符号不能是数字。
3. 长度不能超过240个字符。
4. 不能使用指令名等关键字。
5. 在作用域内必须是惟一的。
3.3.1 标号
1. 标号的定义
当在程序中使用一条跳转指令的时候,可以用标号来表示跳转的目的地,编译器在编译的时候会把它替换成地址,标号既可以定义在目的指令同一行的头部,也可以在目的指令前一行单独用一行定义,标号定义的格式是:
标号名: 目的指令
标号的作用域是当前的子程序,在单个子程序中的标号不能同名,否则编译器不知该用哪个地址,但在不同的子程序中可以有相同名称的标号,这意味着不能从一个子程序中用跳转指令跳到另一个子程序中。
在低版本的MASM中,标号在整个程序中是惟一的,子程序中的标号也可以从整个程序的任何地方转入。但Win32汇编使用的高版本MASM中不允许这样,这是为了提供对局部变量和参数的支持,由于在子程序入口处有对堆栈的初始化指令,所以一个子程序不允许有多个入口,其结果就是标号的作用域变成了单个子程序范围。
2. MASM中的@@
在DOS时代,为标号起名是个麻烦的事情,因为汇编指令用到跳转指令特别多,任何比较和测试等都要涉及跳转,所以在程序中会有很多标号,在整个程序范围内起个不重名的标号要费一番功夫,结果常常用addr1和addr2之类的标号一直延续下去,如果后来要在中间插一个标号,那么就常常出现addr1_1和loop10_5之类奇怪的标号。
实际上,很多标号只会使用一到两次,而且不一定非要起个有意义的名称,如汇编程序中下列代码结构很多:
mov cx,1234h
cmp flag,1
jz loc1
mov cx,1000h
loc1:
…
loop loc1
loc1在别的地方就再也用不到了,对于这种情况,高版本的MASM用@@标号去代替它:
mov cx,1234h
cmp flag,1
jz @F
mov cx,1000h
@@:
…
loop @B
当用@@做标号时,可以用@F和@B来引用它,@F表示本条指令后的第一个@@标号,@B表示本条指令前的第一个@@标号,程序中可以有多个@@标号,@B和@F只寻找匹配最近的一个。
不要在间隔太远的代码中使用@@标号,因为在以后的修改中@@和@B,@F中间可能会被无意中插入一个新的@@,这样一来,@B或@F就会引用到错误的地方去,源程序中@@标号和跳转指令之间的距离最好限制在编辑器能够显示的同一屏幕的范围内。
3.3.2 全局变量
1. 全局变量的定义
全局变量的作用域是整个程序,Win32汇编的全局变量定义在 .data或 .data?段内,可以同时定义变量的类型和长度,格式是:
变量名 类型 初始值1,初始值2,……
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -