2.1 Win32可执行文件的开发过程
在DOS下,生成一个可执行文件的步骤比较简单,用编译器将源程序编译为obj文件,再用链接器将obj文件链接成exe文件,不同语言的开发过程都差不多。
DOS可执行文件中的内容是由源程序中所写的代码和数据定义转换而来的。唯一的例外是带覆盖部分(Overlay)的exe文件,它在基本的exe文件后附加了一些自定义的数据,其中可执行部分的长度由文件头偏移0002h和0004h中的长度给出,该长度之后到文件实际长度这部分就是Overlay部分。这样,即使一个带覆盖的exe文件大小远远超过640 KB,在DOS下也能运行,因为操作系统只装入真正的可执行部分,然后由程序自己去读取覆盖部分的数据。一些打包软件生成的奇大无比的自解压包就采用这种结构,可执行部分是解包代码,覆盖部分是被压缩的数据。DOS对可执行文件覆盖部分的数据格式并没有规定,它是程序员按自己的方式组织的。如果程序员愿意,也可以把这些数据单独放在另外一个文件中。
Win32可执行文件叫做PE文件。PE文件的基本结构和DOS可执行文件有很大的不同。它把程序中的不同部分分成各种节区(Section),其中可以有一个节区是放置各种资源的,如菜单、对话框、位图、光标、图标和声音等(详见第17章)。虽然可以把资源部分理解成类似DOS可执行文件中的“覆盖”部分,但由于资源是Win32可执行文件的标准组成部分,而且是非常重要的组成部分,它的格式是固定的。所以与DOS软件的开发过程相比,Win32软件的开发中多了一个创建资源文件的步骤。
以使用MASM32 SDK软件包为例,在用Win32汇编开发软件的流程中,程序员要做的工作分创建代码和创建资源两部分,如图2.1所示。
图2.1 Win32可执行文件的开发过程
代码部分的开发工作与DOS下写代码的步骤是一样的。程序员用文本编辑器书写汇编源代码(*.asm文件)。与C源代码类似,asm文件中也可以用include语句包含数据定义和函数声明的头文件,Win32汇编的头文件一般用inc作扩展名。大部分的include文件是编译器软件包附带提供的,如MASM32 SDK附带的Windows.inc文件定义了Win32 API中很多参数和数据结构,其他的inc文件则是不同DLL中的Win32 API函数声明。最后,asm文件经汇编编译器编译成以obj为扩展名的目标文件。
资源文件中可以包括对话框、快捷键、菜单、字符串、版本信息和一些图形资源等内容。资源文件的源文件是一种类似“脚本”的文本文件,它的扩展名一般为rc,其中用不同的语法定义了不同类型的资源,资源脚本文件最后由资源编译器编译成资源文件*.res。资源脚本文件同样用到很多预定义值,所以软件包中一般也包括资源头文件供源文件来导入。MASM32 SDK软件包中的资源头文件是Resource.h。
在资源文件中,不同类型资源的记录方式是不同的。对话框资源只记录定义值,如对话框的大小、位置等,并非真正存储对话框最后显示在屏幕上的像素。这些大小、位置等信息最后由Windows解释后才在屏幕上被绘画成像素;菜单、字符串、快捷键等由文本构成;图形资源则真正由像素组成,它们在资源脚本中被定义为一个文件名,由资源编译器从磁盘文件导入。Windows在资源中支持的图形文件有bmp位图文件、cur光标文件和ico图标文件,这些图形文件可以用其他图形处理软件生成。另外,wav声音文件也可以用在资源中。创建资源的方法在第5章中有详细的描述。
编译好目标文件*.obj和资源文件*.res后,最后一步是用链接器将它们链接成可执行文件。链接的时候要用到函数库。在DOS环境下编程的时候,使用的函数库是静态库。静态库是一些已经编译好的代码模块。当用户在源程序中用到某个函数的时候,链接器从库文件中将这个函数的二进制代码取出,与obj文件合在一起生成最终的exe文件。但在Win32环境下,大部分的公用函数封装在DLL文件中,以动态链接的方式供用户程序调用。这时候库文件中只需要包含函数在DLL中的位置信息,不再需要有二进制代码部分。所以链接的时候也只是把库文件中的位置信息取出放入最后的可执行文件中。Win32中这种只包含位置信息的库文件称为导入库。动态链接的概念在第11章中有详细的描述。
由于Win32汇编编程中使用不同汇编编译器的时候,汇编源程序的格式和资源脚本文件的格式可能稍微有所不同。各种头文件、库文件的文件名也有所不同。所以在开始编程之前,必须先选定一种合适的编译器。
2.2 编译器和链接器
选择汇编编译器是开始工作的第一步。不同的编译器用法各不相同,选择合适的编译器可以为开发工作节省很多的时间。这里简单介绍几种不同系列的编译器。常用的汇编编译器有Microsoft公司的MASM系列和Borland公司的TASM系列,还有一些小公司推出的或者免费的汇编软件包。
2.2.1 MASM系列
1.MASM编译器介绍
MASM是Microsoft公司推出的汇编编译器。它的版本从低到高经过了很多次的升级(微软的通病,升级补丁多如牛毛)。每次升级除了例行的错误修正外都增加了一些新的功能,以至于到最后高版本和低版本的语法和功能相差很多,向下兼容性也不好。低版本的MASM固然无法编译高版本的源程序,但高版本的MASM也可能无法正常编译低版本的源程序,如MASM 4.0写的源程序常常无法在MASM 6.x上编译成功。在使用MASM系列编译器时,如果不先搞清楚特定的语法和编译选项可以在哪个版本上用,编译中就会错误连篇。所以在这里有必要了解一下MASM各版本的演变过程。
表2.1列出了不同版本MASM编译器的区别。
表2.1 MASM编译器各版本的区别
不同版本MASM产生的obj文件的格式也不相同,在DOS和Win16时期,Microsoft使用的obj文件格式为OMF格式(Intel Object Module Format),到了Win32时期后改用了COFF格式(Common Object File Format),原因之一是COFF格式更像最终的PE文件,在链接的时候可以做更少的处理,MASM从6.11版本开始支持COFF格式。
用Microsoft的产品编写Win32程序,不管是使用VC还是MASM,都必须使用COFF格式,因为Microsoft的32位的Link只支持将COFF格式的obj文件链接成PE文件,另外所有的导入库等支持文件的格式也全部是COFF格式的。
单独的MASM软件包不是免费的,但免费发布的Windows 98 DDK中却包括完整的MASM 6.11d版本,Win98ddk.exe文件一开始可以在Microsoft的网站上免费下载,现在已经不再提供,但读者还可以在一些第三方的站点找到这个软件包。
注意:整个Win98ddk.exe文件有18 MB之大!得到了MASM 6.11d之后,可以从Microsoft获取升级软件一直升级到最新的版本,升级包的下载地址是:
ftp://ftp.microsoft.com/softlib/mslfiles
升级包的文件名和版本号相对应,如到6.14版本的升级文件是Ml614.exe,迄今为止最新的MASM 6.15版本可以从Visual C++ 6.0 Processor Pack中获取,该软件的下载地址可以在微软的网站上查找。
2.Ml.exe的用法
不同版本的MASM在使用上有很大的不同,本节所指的是可用于Win32汇编编程的MASM 6.14及以上版本,MASM编译器的命令行用法为:
Ml [/选项] 汇编源文件列表 [/link链接选项]
要注意的是汇编选项要集中写在源文件名的前面,比如下面的两条命令:
Ml /c /coff /Cp Test.asm
Ml /c /coff Test.asm /Cp
虽然它们都可以编译Test.asm文件,但第二句的/Cp选项由于写在了汇编源文件名的后面,实际上会被忽略掉。另外,多个选项之间一定要加空格,经常有初学者将多个选项连在一起写成“/c/coff”,结果当然是会报错,因为编译器将它当做一个选项来辨认了。Ml在Win32汇编编程中常用的选项如表2.2所示。
表2.2 Ml的常用选项
与用MASM 5.0及以下的版本编写DOS程序相比,用MASM的高版本编写Win32程序有几个必须使用的选项,如/coff等。另外,用/Zi增加调试信息在源码级调试中也很有用。
3.Link的用法
用Ml.exe编译的COFF格式的obj文件可以用Link.exe链接成可执行PE文件,Microsoft的Link.exe有两个系列的版本,用于链接DOS程序的链接器为Segmented Executable Linker;可以链接Win32 PE文件的链接器为Incremental Linker,这里指的是Incremental Linker的用法。
Link的命令行使用方法为:
Link [选项] [文件列表]
命令行参数中的文件列表用来列出所有需要链接到可执行文件中的模块,可以指定多个obj文件、res资源文件以及导入库文件。Link的选项很多,常用的选项如表2.3所示。
表2.3 Link的常用选项
由表2.3可见,Link的选项远比MASM要复杂,但并不是所有的选项都是频繁使用的,编写普通的Win32可执行文件时,必须用的选项只有/subsystem一个,其他的都可以用默认值。
一般来说,用MASM编译和链接一个Win32汇编源程序常用的命令是:
Ml /c /coff xx.asm
Link /subsystem:windows xx.obj yy.lib zz.res (普通PE文件)
Link /subsystem:console xx.obj yy.lib zz.res (控制台文件)
Link /subsystem:windows /dll /def:aa.def xx.obj yy.lib zz.res(DLL文件)
在Ml中使用/c选项表示只生成obj文件而不是直接产生exe文件,原因是链接的时候可能需要指定资源文件,所以不能让Ml直接用默认的方式链接;/coff选项是必需的,因为链接器只支持COFF格式的obj文件,其他的选项,如/Cp和/Gz虽然也是必需的,但是由于可以在asm源文件中用伪定义设置,所以一般不在命令行中指定,以免遗漏。
使用Link的时候,/subsystem选项必须被指定,一般指定为windows,当编写控制台程序的时候要改为console。写dll的时候要用/def指定列表定义文件,同时要指定/dll选项。其他的一些参数如/stub,/section和/base等只在编写特殊用途的程序时才使用。
2.2.2 TASM系列
1.TASM的用法
TASM是Borland公司推出的汇编编译器,也是一种使用很广泛的编译器,与MASM相比,TASM的升级没有这么频繁。TASM早在1.0版本就有了对80386处理器指令的完全支持(MASM要到5.0版本才支持80386指令),1989年推出的1.01版本修正了1.0版的一些错误;早期的版本还有TASM 3.0和TASM 4.0,其中4.0版是TASM系列编译器编写DOS程序使用最广泛的版本。
到目前为止,TASM的最后一个版本是5.0版,这个版本支持Win32编程,并单独为Win32编程附带有一整套的32位程序:32位的编译器TASM32.EXE、链接器TLINK32.EXE和资源编译器BRC32.EXE。与这些32位程序对应的16位工具在软件包中依然存在,文件名为TASM.EXE,TLINK.EXE和BRC.EXE等。
TASM 5.0命令行的使用方法是:
TASM32 [选项] 源文件名[,[目标文件名],[列表文件名],[索引文件名]][;]
在Win32编程时TASM的常用选项如表2.4所示。
选项 | 简介 |
---|---|
-c | 链接时区分大小写 |
-B:xxxx | 指定可执行文件装入内存的基地址 |
-Txx | 输出文件类型,-Tpe 表示输出PE类型的 exe 文件,-Tpd 表示输出 PE 类型的 dll 文件 |
-ax | 文件类型,-aa表示使用Windows API,-ap 表示使用兼容代码 |
-v | 在输出文件中包括调试信息 |
表2.5 TLINK32的选项
下面是用TASM编译和链接一个Win32汇编源程序的常用命令:
TASM32 /ml /m2 xx.asm
TLINK32-Tpe -aa -c xx.obj,,,yy.lib,,zz.res (普通PE文件)
TLINK32-Tpd -aa -c xx.obj,,,yy.lib,aa.def,zz.res (DLL文件)
由于Windows API区分大小写,所以TASM32的/ml和TLINK32的-c选项必须指定,TLINK32中的-Tpe和-aa选项也必须指定,否则链接出来的就不是Win32可执行文件了。其他的选项如调试信息等则可以根据需要选择使用。
2.2.3 其他编译器
除了MASM和TASM这两种主流的汇编编译器,汇编编程中还可以用到一些其他的编译器,这些编译器大部分是免费的,如表2.6所示。
表2.6 常用汇编编译器列表
值得一提的是NASM,这个编译器也支持Win32汇编,不同于MASM和TASM这两个编译器,它是免费软件并且开放源代码,如果读者对编译器的原理感兴趣的话,可以从网上下载整个NASM的软件包来看一看,NASM的官方站点网址是http://www.nasm.us。
NASM不具有MASM和TASM所拥有的一些高级语法,如将带参数的调用语句自动转化成多个push指令和一个call指令,更没有MASM所有的 .if/.endif等高级语法,这使NASM用于Win32编程相当不方便,整个感觉和用MASM 4.0差不多,几乎所有的细节都需要用户自己写。但NASM的一个显著优点部分地抵消了这个缺点,因为它支持不同的平台,如Windows,Linux和OS/2等,用它写Win32程序虽然有些麻烦,但熟悉了它的语法后可以很快在Linux的汇编中上手,所以使用NASM的程序员还是不少。
2.2.4 MASM,TASM还是NASM
既然编写Win32汇编可以使用的编译器有这么多种,那么我们究竟使用哪一种呢?单从编译器的角度来说,用MASM写汇编程序是最方便的,支持@@标号,用invoke调用子程序,支持局部变量和有 .if/.else /.endif高级语法等优点就已经是足够的理由了,更不用说有Microsoft这个强大的后盾了,这是其他的编译器所无法比拟的,但使用MASM的不方便之处是它从来就不是当做一个完整的软件包发售的,要开始用MASM写Win32程序还要费很大的周折。
首先是不同版本的MASM软件包中都没有包含资源编译器,资源编译器是当做Windows SDK的一部分发行的,或者要从Microsoft Visual Studio软件包的Common目录中找,更有甚者,和MASM软件包一同发售的Link程序竟然不是32位的,只能用来链接DOS程序,即使是6.11以上版本中也是如此。迄今为止,MASM软件包中附带的链接程序全是Segmented Executable Linker,Incremental Linker只能在Microsoft Visual Studio软件包的Visual C++目录中找到。
其他一些有用的工具也没有包含在软件包中,如库管理工具和make工具等,所以要使用MASM进行Win32汇编编程就要对软件包进行改造:一方面舍弃MASM软件包中附带的Link程序;另一方面,需要到其他地方去找资源编译器和32位的链接器等工具软件。
对初学者来说,连Win32汇编的开发要哪几个步骤及需要用什么软件都还没有底,就更不用说从不同的工具包中寻找需要的软件了(而且必须是合适的32位版本);另外,Win32编程用到的导入库在MASM软件包中并没有包括,同样要到Visual C++中去找;最大的障碍在于:MASM软件包中没有头文件,也不可能直接使用Visual C++的头文件,所有这些头文件必须自己根据资料以及参考Visual C++的 .h文件整理出来,而Windows的数据结构和预定义的数据是出了名的多,所有这些让使用MASM编写Win32汇编程序非常难以下手。
从工具包的完整性来说,TASM和NASM相对来说要好一点,TASM软件包中包括了32位的资源编译器和链接器,也有一个32位的导入库文件,这样,用户不用添加任何其他软件就可以直接用TASM写出完整的Win32程序。但TASM软件包中也没有Windows数据结构和预定义的头文件,所有资料同样需要用户自己整理。
另一方面,与MASM相比,TASM在优化方面做得不是很好,简单举几个例子:比如TASM无法处理大量的预定义,如果用户把所有的预定义整理到Windows.inc文件中,然后在源文件中包括进来,编译的时候就会出现“Out of hash space”错误,结果每次只好把要用到的定义分拣出来写成一个小的include文件;再比如在源程序中用extrn定义API函数,不管在程序中实际有没有用到这个函数,TASM都会在最后 .exe文件的导入表中加上这个函数名,这就意味着无法用偷懒的办法把所有的API函数声明写到一个include文件中去,除非用户可以忍受可执行文件中无效的字节数比有效的多得多!还有一个缺点是:TASM在定义结构的时候,不同的结构中不能有同名的字段,而Windows的数据结构定义出奇的多,结果不同结构中的同名字段要在前面加上一些前缀以示区别,这就会使源代码中的结构定义和参考资料中的结构定义在字面上不符合,使用时还要不停地去看结构中的字段究竟是怎么定义的。这些小缺点使TASM在使用时程序员做的无效工作比有效工作还多。
NASM的优点就是学到的语法可以直接用在Linux的汇编中,缺点也是显而易见的,就是免费软件往往缺乏强大的后盾,开发的力度肯定不如大的软件公司,具体就表现在NASM中几乎没有一点可以帮程序员省心的高级语法,而这些恰恰是编写高可维护性程序所必须具有的特征。而且,用NASM编程同样存在用户自己整理数据结构定义和预定义的问题。
比较这些编译器,可以发现很难找到直接拿来就可以开始写Win32汇编程序的软件包,因为每个软件包中都没有关键的头文件,而用户自己整理头文件不仅使程序的可移植性大打折扣,而且工程量之大使程序员只能写很小规模的程序,所以,理想的软件包应该是这样的:
● 包含所有所需工具,如汇编编译器、资源编译器和链接器等。
● 编译器支持高级语法,使源程序便于维护。
● 包含完整的头文件,如Windows的数据结构定义和预定义等。
● 包含齐全的导入库。
● 有大量的例子和说明文档。
有这样的汇编软件包吗?有!MASM32 SDK软件包就是我们的选择。
2.2.5 我们的选择——MASM32 SDK软件包
读者可能会感到奇怪,怎么又出来一个MASM32 SDK,这是什么公司的产品呢?实际上,MASM32 SDK是不同工具软件的大集合,它的汇编编译器用的是微软MASM软件包中的Ml.exe,资源编译器和32位链接器使用的是Microsoft Visual Studio中的Rc.exe和Link.exe,同时包含了Microsoft Visual Studio中的其他一些工具,如Lib.exe和DumpPe.exe等,所有的工具都是适合于Win32编程的版本。
同时,MASM32 SDK软件包包括了详尽的头文件和导入库文件,导入库文件取自Visual C++的导入库,规模庞大的头文件则是发布者整理的,软件包中还包括了很多的例子,涉及Win32汇编的很多方面,例子收集自世界各地Win32汇编爱好者发布的源程序。为了使工具包更实用,发布者还为它编写了一个简单的IDE环境,包括一个专用的汇编源程序编辑器和源程序模板生成器等。
MASM32 SDK软件包使汇编不再只用来编写简单的程序和少量的核心模块,它的目标完全是为了用汇编写出专业的大型程序。虽然它是一个大杂烩,但发布者做了所有汇编程序员都想做、却又在庞大的工程量前止步的工作——收集合适的工具软件、收集导入库、整理出完整的头文件、收集例子文件、写帮助文档……
让我们感谢发布者Steve Hutchesson为所有的Win32汇编程序员所做的这一切。
迄今为止,MASM32 SDK的最高版本是MASM32 SDK Version 11(简称MASM32V11),与早一些的MASM32V7和MASM32V6版本相比,使用的编译器等可执行文件并没有什么改变,不同的地方是在头文件中增补了一些数据结构定义和增加了不少例子程序。最新版本的MASM32 SDK软件包可以在官方网站MASM32 SDK中下载。MASM32 SDK是一个免费的软件包,但其中的不同部分如编译器和例子程序等可能属于不同的公司和个人,使用时需要遵从他们的版权声明。MASM32 SDK的安装界面如图2.2所示。
图2.2 MASM32的安装界面
安装MASM32 SDK时,在选择目标驱动器后,工具包会被安装到根目录下的MASM32目录中。读者需要知道一些重要文件的位置,这在使用时非常重要,MASM32下各目录的列表和说明如表2.7所示。
表2.7 MASM32 SDK软件包的安装目录说明
如果不用内带的IDE环境,不看附带的例子和帮助文件,那么有了bin,include和lib这三个目录中的内容,读者就可以进行Win32汇编编程了,其他目录中的文件仅起辅助作用。
本书的编程环境就是以MASM32 SDK软件包为基础的,事实上,现在MASM32 SDK已经是最流行的Win32汇编开发包,世界上大部分的Win32汇编程序员都用它来进行Win32软件开发。
2.3 创建资源
2.3.1 资源编译器的使用
资源编译器用来把资源脚本文件(*.rc)编译成资源文件(*.res),MASM32 SDK软件包中使用的是Visual C++附带的Rc.exe程序,TASM软件包中使用的是BRC32.exe或BRCC32.exe,两者的用法都比较简单,它们有相同的命令行语法:
Rc或BRC32 [选项] 资源脚本文件名
Rc和BRC32在使用中没有必需的选项,不像汇编编译器一样必须使用一些关键的选项。如果编译成功,就会产生以res为扩展名的资源文件,两者生成的资源文件的格式是一样的。
资源文件编写是PE开发的标准步骤,由于不同的语言使用的资源编译器,以及生成的.res文件格式都是一样的,没有汇编格式的资源文件和C格式的资源文件之分,所以汇编开发包中的资源编译器实际上就是C开发包中的资源编译器。由于C语言的使用远比汇编广泛,所以资源脚本文件的语法是C格式的,如等值定义语句使用#define而不是汇编常用的equ,注释使用“//”而不是“;”,头文件习惯使用 .h扩展名而不是 .inc,参数定义有“或”操作时使用“|”操作符而不是汇编的“or”操作符等,这些在使用中必须注意,否则会引起语法错误。
两种资源编译器在使用中稍微有所区别,由于BRC32.exe内部可以解释Windows的一些预定义值,所以不用附带头文件,只有遇到最新的预定义时才需要头文件,而Rc.exe并没有这个功能,所以在脚本文件中必须把头文件Resource.h包括进去。
2.3.2 所见即所得的资源编辑器
资源脚本文件中一个典型的对话框定义是这样的:
DLG_MAIN DIALOG 0, 0, 176, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION |WS_SYSMENU
CAPTION "对话框模板"
FONT 9, "宋体"
{
DEFPUSHBUTTON "退出",IDOK,120,46,50,14
CONTROL"",-1,"Static",SS_ETCHEDHORZ|WS_CHILD|WS_VISIBLE,7,38,164,2
}
第一句定义了对话框的左上角坐标为(0,0),大小为(176,66),中间的DEFPUSHBUTTON定义了一个位置为(120,46),大小为(50,14)的按钮,看到这些定义后脑袋里要浮现一个正确的对话框是很不容易的,只能在纸上打格子画出来后才能明白它的模样,所以直接用文本编辑器书写资源脚本文件有诸多不便,就像谁都不会用db语句一个个像素地定义bmp位图一样,写资源脚本同样需要像图形编辑软件一样所见即所得的工具。Borland公司的Resource Workshop和VC环境内带的资源编辑器就是这样的工具。
1.Resource Workshop资源编辑器
Resource Workshop是Borland公司出品的资源编辑器,它是当做Borland C++的一个组成部分发布的,并不是一个单独的产品,但由于它使用方便,应用一直比较广泛,于是有人将它单独分离出来做成一个软件包下载,Resource Workshop的初始版本不是双字节版,所以无法支持中文,在编辑的时候输入中文,存盘后会变成乱码,非常不便,还是有“好事者”将它修改成了双字节版供人下载,在网上搜寻下载的时候要注意版本区别。
同样是上面举例的对话框,在Resource Workshop中的编辑界面如图2.3所示,是不是形象多了?Resource Workshop不但支持资源脚本文件*.rc,同时支持资源文件*.res,读者可以用它打开*.res文件,编辑后存为*.rc文件,同时也可以直接用它编辑*.rc文件且生成*.res文件,这样就可以免去用资源编译器编译脚本文件的步骤。
图2.3 Resource Workshop的使用界面
奇怪的是,BRCC32.exe不需要预定义头文件的支持,而同是Borland公司的Resource Workshop却没有内置窗口风格等定义值,所以在使用中需要Resource.h头文件的支持,并且,Resource.h文件必须和 .rc文件在同一个目录中,所以用它来编辑资源脚本文件的时候要注意拷贝一份Resource.h文件,当然,如果直接用它来编辑资源文件*.res则不需要头文件。
Resource Workshop的缺点是版本比较老,毕竟它是随早期的Borland C++发布的,所以它不支持一些新的特征,如窗口的扩展风格等,如果在 .rc文件的定义中用到这些风格,Resource Workshop会提示不认识这些关键字,用它打开含扩展风格对话框的 .res文件,则会提示一个Unexpected file format错误。
2.用Visual C++编辑资源
Visual C++本身是一个很大的软件包,里面集成了资源编辑功能,它也是所见即所得的编辑工具,并且支持最新的资源特征,如最新的对话框风格和一些新的控件等,同时它是双字节版本,不必担心乱码问题,Visual C++也支持编辑脚本文件*.rc和资源文件*.res,可以在两者之间互相转换。单从功能方面考虑,用Visual C++来编辑资源是一个很好的选择。Visual C++的资源编辑界面如图2.4所示。
图2.4 Visual C++的资源编辑界面
使用Visual C++做资源编辑器的一个显著缺点就是它的规模,资源编辑的功能是集成在IDE环境中的,要使用它就要安装整个Visual C++软件包,至少需要几百MB的空间!而Resource Workshop只有几MB,MASM32 SDK软件包也只有不到10 MB,为了一个资源编辑功能用去几百MB的空间似乎有点好笑。
另外,用Visual C++生成的.rc文件总是包含了很多VC自己的头文件,如果将它们去掉,下次就无法再用VC打开;如果不去掉这些多余的信息,那么用Rc.exe编译的时候就要把所有需要的头文件拷贝过来,将源程序和别人交流的时候,别人要编译这个资源脚本也必须到VC中去找到这些头文件。
但Visual C++毕竟是个功能强大的工具,建议读者还是使用Visual C++来编辑资源,存盘的时候直接存为*.res文件,这样可以省去编译资源的步骤。到最后调试完成的时候或者需要交流的时候,可以保存一份 .rc文件并将文件中VC使用的多余内容去掉,整理成Rc.exe可以编译的格式。
2.4 make工具的用法
2.4.1 make工具是什么
在DOS时期编写汇编程序的时候,编译器和链接器基本上不用什么参数,命令只有区区两条:
Masm xxx.asm;
Link xxx.obj;
只要做个批处理把xxx换成%1,然后在命令行键入asm.bat xxx就万事大吉了,很是方便。Win32编程就不一样了,不管编译器还是链接器都需要加上必要的选项,文件列表也多了起来,如链接器的命令行参数中要列出obj,lib,res和def等多种文件,又多了资源编译这一步,如果用批处理实现,要加的参数太多太乱,而每次用手工一行行地键入命令的话,那对程序员来说简直就是一场灾难。当然,一种简单的解决办法就是为每个编程项目单独建立一个批处理,每次改动后,运行批处理把所有模块重新编译一次,但是当程序很庞大的时候,这将花费很长时间,那么该如何处理呢?这时候就要用到make工具来维护代码了,从网上下载Win32汇编的例子程序时,常常发现除了*.asm和*.rc文件外,例子文件包中往往还有一个makefile文件,这就是给make工具用的。
make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式——通过调用makefile文件中用户指定的命令来进行编译和链接的。但是,批处理会执行全部命令将全部源文件编译,包括那些不必重新编译的源文件,而make工具则可根据目标文件上一次编译的时间和所依赖的源文件的更新时间自动判断应当编译哪些源文件,对没有更新过的文件不会处理,这样就可以大大提高程序调试的效率。
举例说明,我们要写一个test.exe文件,生成最后的可执行文件有4个步骤:
(1)汇编源文件x.asm,其中用到头文件common.inc,它们经Ml.exe编译成x.obj;
(2)汇编源文件y.asm,用到头文件common.inc和y.inc,它们经Ml.exe编译成y.obj;
(3)资源脚本文件x.rc,经Rc.exe编译成x.res;
(4)最后用Link将x.obj,y.obj和x.res链接成test.exe。
可以看出,当程序调试的时候,如果修改了x.asm,也就是说x.obj的文件时间比x.asm要早,就需要重新执行步骤(1)和(4);如果修改了y.asm或y.inc,那么需要重新执行步骤(2)和(4);如果修改的是x.rc,则步骤(3)和(4)必须重新执行;如果修改的是common.inc,因为x.asm和y.asm都和它有关,所以步骤(1)、(2)和(4)都要重新执行;如果同时修改了common.inc和x.rc,那么必须重复全部步骤。在这个例子中,文件的依赖关系就是:
● test.exe依赖于x.obj,y.obj和x.res;
● x.res依赖于x.rc;
● x.obj依赖于x.asm和common.inc;
● y.obj依赖于y.asm,common.inc和y.inc。
make可以根据文件的时间正确判断文件的新旧并执行相应的步骤。但make又是如何知道文件之间的依赖关系呢?这需要用户用一个描述文件来指定。前面提到的makefile就是这个描述文件,执行make工具的时候,它会默认用makefile做描述文件名来进行相应的工作,书写描述文件有规定的语法,虽然语法不是很简单,但写好以后就省事多了。
Microsoft的make工具文件名为nmake.exe,它并不是MASM软件包的一部分,但可以在Visual C++的Bin目录下找到。Borland公司的make工具文件名是make.exe,它已经包括在TASM 5.0工具包中。两者默认的描述文件名都是makefile,描述文件的语法也大同小异,只是使用时命令行参数有些不同。
2.4.2 nmake的用法
在命令行键入nmake /? 可以显示帮助信息,nmake的语法如下:
nmake [选项] [/f描述文件名] [/x输出信息文件名] [宏定义] [目标]
说明如下:
● /f参数——如果描述文件名不使用默认的makefile,可以用/f参数指定。
● /x参数——如果想把屏幕输出的信息存到一个文件中,可以用/x参数指定(用DOS下的管道操作符nmake > 文件名的方法无效)。
● 宏定义——可以用新的定义覆盖描述文件中的宏定义。
● 目标——指定建立描述文件中描述的某个文件,如上面的例子中默认是生成最后的test.exe文件,也可以用nmake x.res指定更新x.res文件。
nmake常用的选项如表2.8所示。
由于nmake的应用是基于文件时间的,当计算机的时钟不准确或文件拷贝到另一台计算机后文件时间有些偏差,那么文件的更新可能会不正确,这时最好用/A选项强制把所有文件更新一遍。在平时使用的时候,以makefile当做建立的描述文件名,那么仅键入不加参数的nmake命令就可以完成所有工作了。
表2.8 nmake的常用选项
2.4.3 描述文件的语法
make工具最主要也是最基本的功能就是通过描述文件来描述源程序之间的相互关系并自动维护编译工作,而描述文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并链接生成可执行文件,并要求定义源文件之间的依赖关系,为了更方便使用,文件中同时可以用一些宏定义。描述文件一般需要包含以下内容:
● 注释
● 宏定义
● 显式规则
● 隐含规则
在这里,首先为2.4.1节中有关test.exe的例子写出一个描述文件,再逐步介绍各部分的书写语法。为了方便使用,一般都把描述文件的文件名取为默认文件名:makefile。这个例子的makefile文件如下(注意前面括号里的是行号,不是文件的真正内容):
(001) # nmake工具的描述文件例子
(002) EXE = Test.exe #指定输出文件
(003) OBJS = x.obj \
(004) y.obj #需要的目标文件
(005) RES = x.res #需要的资源文件
(006)
(007) LINK_FLAG = /subsystem:windows #链接选项
(008) ML_FLAG = /c /coff #编译选项
(009)
(010) #定义依赖关系和执行命令
(011) $(EXE): $(OBJS) $(RES)
(012) Link $(LINK_FLAG) /out:$(EXE) $(OBJS) $(RES)
(013) $(OBJS): Common.inc
(014) y.obj: y.inc
(015)
(016) #定义汇编编译和资源编译的默认规则
(017) .asm.obj:
(018) ml $(ML_FLAG) $<
(019) .rc.res:
(020) rc $<
(021)
(022) #清除临时文件
(023) clean:
(024) del *.obj
(025) del *.res
1.注释和换行
makefile中的注释是以#号开头一直到行尾的字符,当nmake工具处理到这些字符的时候,它会完全忽略#号及其后面的全部字符。
当一行的内容过长的时候,可以用换行符来继续,makefile的换行符是\,如例子中的第3行和第4行可以合并为:
OBJS = x.obj y.obj #需要的目标文件
在使用换行符的时候要注意在“\”后面不能再加上其他字符,包括注释和空格,否则nmake检测到“\”不在一行的最后,就不会把它当成换行符解释,从而出现错误。
2.宏定义
makefile中允许使用简单的宏定义指代源文件及其相关编译信息,可以把宏称为变量,在整个描述文件中,只要符合下面语法的行就是宏定义:
变量名=变量内容
如上面例子文件中的第2到第8行就是宏定义,在引用宏时只需在变量前加$符号,但是要注意的是,如果变量名的长度超过一个字符,在引用时就必须加圆括号(),下面都是有效的宏引用:
$(LINK_FLAG)
$(EXE)
$A
$(A)
其中最后两个引用是完全一致的。
宏定义的使用可以使makefile的使用更灵活:首先可以使文件便于修改,比如把第8行和第18行中ml的选项部分写成宏定义,以后要改变编译选项的时候,只要直接在makefile文件头部改变宏定义就可以了,不必阅读修改整个makefile文件;其次,当不止一个地方用到同一个文件的时候,把文件名定义为宏定义可以减少错误,增加可读性,同时也可以便于修改;最大的好处是可以直接在命令行中用新的宏定义覆盖,比如在命令行中键入:
nmake ML_FLAG="/c /coff /Fl"
那么这时就会以新的/c /coff /Fl定义代替makefile中定义的/c /coff,在这种使用中要注意两个问题:一是宏名称要区分大小写,ML_FLAG和ml_flag是不一样的;二是定义值中有空格的时候要用双引号引起来(没有空格时可以不用双引号,如ML_FLAG=/c),这使临时使用不同的参数编译文件时可以不必修改makefile。
3.显式规则
makefile中包含有一些规则,这些规则定义了文件之间的依赖关系和产生命令,一个规则的格式是这样的:
目标文件:依赖文件;命令 (方法1)
或
目标文件:依赖文件 (方法2) 命令
在规则定义和命令行中,不能包含注释,例子中的第11和12行把宏定义展开后就是:
test.exe: x.obj y.obj x.res
Link /subsystem:windows /out:test.exe x.obj y.obj x.res
这里的目标文件就是test.exe,它依赖于3个文件x.obj,y.obj和x.res,如果有必要,产生目标文件的命令就是下面的Link命令。规则可以用两种方法,用方法2的时候,命令可以从第2行开始,第1行的“;”省略,但是这时命令前面必须有一个Tab字符,否则nmake无法区分这究竟是命令还是别的定义。
在同一个规则中,目标文件可以有多个,依赖文件也可以有多个,同时命令也可以由多个命令行组成,当然这时候就必须用第二种方法定义了,否则无法在同一行中写入多条命令。
我们也可以用和上例中类似的方法定义其他规则,如x.obj或x.res的生成方法,但nmake如何知道哪个是最终要make的文件呢?实际上nmake默认将整个描述文件的第一条规则中的目标文件认为是最终文件,如果我们把第11,12行放到第13行后面,那么x.obj和y.obj的建立规则就成了第一条规则,nmake建立了x.obj和x.obj之后就不理会test.exe的建立了,所以我们必须把最终需要生成的文件放在第一条规则定义。当然,在nmake的命令行参数中可以指定要make的目标,如我们只需生成x.res文件,那么不必修改makefile将x.res的描述规则移动到最前面,而是直接在命令行键入以下命令即可:
nmake x.res
参数中也可以同时带好几个目标文件名,nmake会一一处理,如果指定的目标文件没有对应的规则,nmake会返回一个出错信息:
fatal error U1073: don't know how to make 'xxx文件'
当用户要求nmake去建造一个目标时,make会去找到这个目标的依赖规则,这时规则中定义的命令并不会立刻被执行,而是首先要做一些事情:nmake先去检查依赖文件是否是另一条规则的目标文件,如果是,则先处理这一条规则;如果不是,nmake再检查各个依赖文件的时间,看这些文件有没有比目标文件更新的,如果没有,nmake会决定不再重新建造目标文件,并给出提示:'xxx文件' is up-to-date,如果依赖文件有比目标文件更新的,才执行命令。
所以一个顺序下来,所有的目标文件,以及它们的依赖文件,以及依赖文件的依赖文件都会被检查并更新,总而言之,一个目标文件的建立包含了顺序正确的指令链接,这个链接结构是树状的,目标文件是根,一级级扩展到多个文件,我们要求的是nmake去建立链接中处于根部的那个文件,nmake会根据链接结构从目标开始向初始状态前进,最后慢慢回来,在这个过程中执行建立每个文件所必需的命令,一直到最终目标建立完成。
目标也可以没有依赖文件,而且目标也可以不是一个真正存在的文件,如例子第23行到第25行中的clean是一个目标,但我们并不是要生成一个clean文件,而是希望在文件调试完毕后用nmake来清除临时文件,当我们键入nmake clean的时候,工作目录下并没有clean这个文件,那么nmake就会去执行clean定义中的命令,因为nmake把每一个不存在的目标当做是一个过时的目标,如此一来,就会删除中间过程中的文件*.obj和*.res。
指出了目标文件全名的规则称为显式规则,但有些类别的文件的编译方法可以是雷同的,如从asm文件产生obj文件的命令总是用ml,从rc文件产生res文件的命令总是用rc,对于每个文件都写一条规则有些多余,这时候就要用到隐含规则。
4.隐含规则
隐含规则可以为某一类的文件指出建立的命令,它具体定义了如何将带一个特定扩展名的文件转换成具有另一种扩展名的文件,定义的格式是:
.源扩展名.目标扩展名:;命令 (方法1)
或
.源扩展名.目标扩展名: (方法2)
命令
隐含规则的语法和显式规则相似,也是用“:”隔开,在“;”下面书写命令,也可以不用“;”而将命令写在第2行,同理,这时命令之前要加一个Tab字符。
隐含规则不能有依赖文件,所以“:”下面没有内容,例子中的第17、18行定义了从asm文件建立obj文件的隐含规则,第19和20行定义了从rc文件建立res文件的隐含规则,隐含规则中无法指定确定的输入文件名,因为输入文件名是泛指的有相同扩展名的一整类文件,这时候就要用到几个特殊的内定宏来指定文件名,这些宏是$@,$*,$?和$<,它们的含义如下:
● $@ —— 全路径的目标文件。
● $* —— 除去扩展名的全路径的目标文件。
● $? —— 所有源文件名。
● $< —— 源文件名(只能用在隐含规则中)。
所以第19、20行中的rc $< 用于x.rc的时候就是rc x.rc,而用于y.rc时就是rc y.rc了。
读者可以注意到一些显式规则没有命令行,如第13行的“$(OBJS): Common.inc”指出了所有的obj文件都依赖于Common.inc文件,第14行的“y.obj: y.inc”则指出了y.obj除了依赖第13行的规则外,还依赖于y.inc。但是第13行和第14行的两条规则都没有指出产生这些obj文件的命令,所以nmake处理的时候会到隐含规则中去找命令行,最后会用第18行的“ml $(ML_FLAG) $<”命令去产生这些obj文件。
2.5 获取资料
对于程序员来说,“高手”和“菜鸟”之间的区别实际上只有两个因素:第一个因素是从事编程时间的长短不同,使经验的多少有所区别;第二个因素就是手头掌握资料的多少了,因为很多问题并不是靠自己钻研可以解决的,必须靠资料,试想在写DOS汇编程序时如果没有中断手册,可以自己钻研出来吗?实际上,大部分“菜鸟”向“高手”问的问题完全可以由参考资料解决,即使一个“菜鸟”对某个问题暂时不懂,但手头有解决问题的详细资料,经过一段时间的钻研,问题自然会解决。“高手”就是这样慢慢练成的。
在Win32汇编编程中,资料显得尤其重要。在DOS时代,整个操作系统的大小不过几十KB,所有的BASIC和C命令基本上都可以直接用人脑记忆下来,用于汇编编程的中断手册也基本上可以让人记住常用的部分。
不过,当时钟走到21世纪的时候,软件规模飞速膨胀,仅是开发工具就动辄几十MB,更不用说复杂的操作系统及其他软件了,所以现在完成一件最基本的事情都必须从文档中寻找合适的方法,大部分程序员手边的文档比字典还要厚几倍,并且,在这些浩如烟海的文档中苦苦寻找之后,还不一定能找出一个满意的解答。像Windows就是一个数据结构的迷宫,其API的资料远比DOS下的中断资料要多,在这种情况下,程序员的经验可以在程序的优化和调试方面发挥作用,但如果没有资料,连程序都写不出来,就更谈不到优化和调试了。
在硬件方面,处理器的发展也很快,图书市场上的资料往往要慢一个节拍,国内的图书尤其如此,想了解最新的指令集就必须到厂家的网站上下载最新资料。所以程序员需要一个强大的信息网络来方便信息的查找,方便与软件开发商的交流,特别是通过Internet。
目前,各大软件开发商都具有各自的程序员信息网络,这些网络能为程序员提供特别的服务和帮助。所以要寻找编程资料,首选方案就是常接触这些网络,如Borland公司的Borland Community,Oracle公司的Oracle Technical Network(OTN),Sun公司的Sun Developer,以及IBM公司的DeveloperWorks等。
2.5.1 Windows资料的来源
要获取Windows的资料自然要到它的老窝——Microsoft的站点上去,Microsoft的程序员网络是MSDN(Microsoft Developers Network),在这里可以获得微软所有产品和操作系统的相关信息。它的网址是Microsoft Learn: Build skills that open doors in your career。
MSDN是一个内容非常全面的信息网络。现在这个网络一共有300万注册用户,它不仅在因特网上建立了网站,并且也发行MSDN杂志,以及可供订阅的CD和DVD,其中包括编程信息、技术论文、操作系统、文档、工具、程序代码,以及新产品的Beta测试包。MSDN的技术支持方式既有免费信息服务,也有收费的服务,例如,订阅MSDN的印刷品,以及购买MSDN的CD和DVD等,购买MSDN实际上等于购买了一种服务。
MSDN的收费服务是以订阅形式出现的一年4期的光盘资料库,它有3个版本:
● MSDN开发库:有知识库和一些例子代码,规模为20张左右的光盘,可以联机检索,一般可以从这里找到全部的API资料、大量的基础知识和代码。
● MSDN专业版:包括MSDN开发库的全部内容,再加上Microsoft操作系统类软件,SDK(Software Development Kit)和DDK(Device Driver Development Kit)。SDK和DDK是软件开发包和驱动程序开发包,它们包括开发软件或驱动程序的头文件、例子,以及一些开发工具,要想知道一个课题的最佳解决方案就是去看对应的SDK和DDK中的例子文件。
● MSDN宇宙版:包括MSDN专业版的内容,还包括Microsoft所有软件,如Windows 2000,Office和SQL Server等,订阅宇宙版可以让用户从一个软件的Beta版时就开始评估使用,从而使开发人员在第一时间内接触到最新的技术与资料,但这些产品只允许用于测试目的,不能用于企业环境去架构网络。
MSDN的订阅费用不菲,3个版本每年的订阅费用分别为1500元、8200元和29000元,这显然是一笔不小的费用,所以很多程序员还是选择在Microsoft的站点上使用联机版本,MSDN站点在内容上显得稍微过繁,有人对此的评论是:“Microsoft每次都会提供大量的资料,以至于可能需要费些力气才能找到所需的东西,不过这总比什么都不提供强”。
从网上也可以找到MSDN的各种独立部分分别下载,如各种版本的SDK和DDK等,当然这不会在Microsoft自己的站点上。同时从网上也可以找到一些单独分离出来的帮助文件,如《Microsoft Win32 Programmer's Reference》,《Win32 Multimedia Programmer's Reference》,《OpenGL Programmer's Reference》,《Windows Sockets2 Application Program Interface》以及其他几乎所有的程序员手册,它们中间包括了对应的API函数的详细资料。
使用这些Windows资料时要注意它们几乎全部是以C语言的语法提供的,因为在Win32的环境下,不管是什么语言,全部都是建立在Win32 API的基础上的,而Windows本身就是用C开发的。我们要写Win32汇编程序,参考资料也只好使用这些C的版本,这就要求读者对C语言中函数的定义、数据类型和数据结构的定义等有基础的了解。也正因如此,如果读者有用MFC编写Windows程序的经验,看完了这本书以后一定会说:“汇编、C、Windows,怎么是同一回事?”的确,在Win32环境下,所有的语言实际上是一回事,只不过Visual FoxPro,Visual BASIC等软件对API以及Windows的消息体系封装很深,Visual C++和C++ Builder等软件相对少一点,而汇编语言则不加任何封装。
有了足够的参考资料以后,并不代表着就可以用汇编编写出常用的Win32程序了,因为毕竟这些只是金字塔的一个底边而已,爬上去的路就是学习的过程,中间最好的参考就是Win32汇编的教程和例子,Internet上有很多的站点是关于Win32汇编编程的,这里列出几个站点,读者可以从这些站点的链接中找到其他很多的相关站点:
● MASM32 SDK软件包的官方站点——http://www.masm32.com
包括MASM32 SDK软件包下载、简单的Win32汇编例子和一些网站链接。
● Iczelion的Win32汇编站点——http://win32asm.cjb.net
最著名的英文Win32汇编站点,包括Iczelion书写的Win32汇编教程、大量的例子和一个讨论区。这个网站需要用代理服务器访问。
2.5.2 Intel处理器资料
Win32汇编参考资料另一个重要部分是Intel处理器的资料,这些资料大部分可以在Intel的官方网站上找到,网址是http://www.intel.com,但Intel的网站存在和Microsoft的网站同样的问题,就是资料“太多”了反而不容易查找。
Intel发布的资料大部分是以PDF格式出现的,每一类文件有唯一的编号,可以用编号或资料名称从网站的搜索栏中找到对应的PDF文件,和Win32汇编编程密切相关的是处理器结构和指令集的参考资料—Intel处理器软件开发员手册《Intel Architecture Software Developer's Manual》,它由3个部分组成。
● 第一部分:基本体系(Basic Architecture),编号24547004;
● 第二部分:指令集参考(Instruction Set Reference),编号24547104;
● 第三部分:编程指南(System Programming Guide),编号24547204。
编号中的前缀245470、245471和245472是文件编号,后面的04表示修订版本是第4次,读者可以在Intel的网站中输入文件编号找到这几个文件并下载使用。它们包括了最新的MMX和SSE指令的用法。
2.6 构建编程环境
由于Win32汇编可以采用多种编译软件,它们的环境设置方法各不相同,对已经入门的读者来说,这不是问题,但初学者往往不能很好地掌握设置的方法,以至于拿到例子程序后编译不出来,不少人往往在例子一而再、再而三编译不出来后深受打击,结果还没有来得及看到汇编的一点影子就“告别”了这个神秘又精彩的世界。
本书的例子是基于MASM32 SDK软件包的,本节介绍该软件包使用中的一些问题。
2.6.1 IDE还是命令行
IDE(Integrated Develope Environment)即集成开发环境,是指在同一个界面中完成从编写源代码到编译,最后到链接的全过程。Microsoft的Visual Studio中的VC和VB等开发环境就是IDE的典型例子,MASM32 SDK软件包中同样有一个简单的IDE环境Qeditor.exe,这个IDE环境实际上只是一个简单的文本编辑器加上一个用户可以自行设置菜单的Shell,编译链接工作由Qeditor.exe在后台调用Ml.exe和Link.exe等软件完成。
如果要使用这个IDE环境,最大的代价就是不得不使用这个简单的编辑器,而一个好的文本编辑器对工作效率的影响是很大的,一个完善的文本编辑器必须包括语法高亮显示、强大的查找替换、无限次Undo和Redo操作、支持特大型的源文件等功能,MASM32 SDK中简单的Qeditor.exe符合不了这些要求。
所以建议读者还是抛弃这个IDE环境,用一个功能强大的文本编辑软件来写源程序,然后在命令行环境中用nmake来维护代码,这样有一个额外的好处,就是保存下来的makefile文件记录了文件的编译与链接参数,可以在以后方便维护。
这里介绍两个很适合用来编辑汇编源文件的文本编辑软件:
● EditPlus——这是一个为程序员编写的文本编辑软件,内置HTML,CSS,PHP,ASP,Perl,C/C++,Java,JavaScript和VBScript的语法高亮显示,也可以下载ASM语法文件,它包括了所有文本编辑软件应该具有的功能:自定义工具菜单,显示行号,语法自动完成,列选择功能(以前只在WPS中看到过此功能),无限次Undo/Redo和语法检查等功能。读者可以从EditPlus - Text editor with FTP, FTPS and sftp capabilities下载试用版,试用版可以通过输入注册码成为正式版本。
● UltraEdit32——这是一个文本编辑/十六进制编辑软件,实现的功能和EditPlus大同小异,另外还有十六进制编辑功能。UltraEdit32也是一个共享软件,可以从UltraEdit Text Editor - Secure, Configurable, Powerful下载,同样可以通过输入注册码成为正式版本。
2.6.2 本书推荐的工作环境
开发用的操作系统推荐使用32位的Windows 7系统。如果使用64位的Windows系统,安装MASM32 SDK软件包时一定要在32位系统中安装好再拷贝到64位系统,因为安装时会动态生成LIB文件,在64位系统下生成的LIB文件会缺少某些32位系统特有的API,导致部分例子编译出错。
本书建议读者放弃MASM32 SDK自带的简单的IDE环境,改为在命令行下用nmake工具进行代码维护,为了建立这个环境,需要做下面的工作。
第1步:安装常用软件,包括编辑软件Editplus、MSDN、十六进制编辑器Hexedit、可视化资源编辑器Resource Workshop、调试工具Soft-ICE和反汇编软件W32DASM等,如果硬盘空间允许的话,最好安装Visual C++,以便使用它集成的资源编辑器。
第2步:选择一个驱动器安装MASM32 SDK软件包,假设软件包安装于x盘,那么安装好的目录是x:\Masm32目录,对读者来说整个软件包中重要的只有3个目录:bin目录中有汇编编译器ml.exe,资源编译器rc.exe和链接器Link.exe等执行文件;include目录中有各种头文件;lib目录中有全部导入库。虽然安装文件自动把安装目录名定为masm32,如果不满意的话,完全可以把这3个关键目录拷贝到别的自己命名的目录中,对使用没有任何影响。
第3步:建立源文件目录,由于Win32汇编不再像DOS汇编一样一个项目只有一个asm文件,而是包括asm,rc,makefile和图标等多个文件,如果把多个项目的文件混在同一个目录中将无法分辨,所以必须为每个项目单独建立一个目录,建议把这些目录集中在一个专门放置源程序的目录中,如x:\Source目录。
第4步:由于MASM32 SDK软件包中没有nmake.exe文件,所以要单独寻找nmake.exe并拷贝到bin目录中。
第5步:为这个环境建立一个设置环境变量的批处理文件,假设文件名为Var.bat,那么这个文件内容如下:
@echo off
set include=x:\masm32\Include
set lib=x:\masm32\lib
set path=x:\masm32\bin;%path%
echo on
文件中设置了3个环境变量:
● include变量指定头文件的搜索目录。定义了这个环境变量后,Ml.exe和Rc.exe在处理asm和rc文件中遇到include语句时,会自动在环境变量定义的目录中查找include语句指定的文件,这样include语句中就不必写头文件的全路径名,如下所示:
include c:\masm32\include\windows.inc 不设置include环境变量时的写法
include windows.inc 设置include环境变量后可以这样写
这样处理的好处是以后移动了MASM32的安装位置后不必修改每个源文件中的include语句。如果使用Visual C++的集成环境来建立rc文件的话,为了使rc.exe能找到头文件,还要把VC++安装目录下的Include和MFC\Include目录包含进来,多个路径之间用“;”隔开:
set include=x:\masm32\Include;VC目录\Include;VC目录\MFC\Include
VC++安装目录一般为C:\Program Files\Microsoft Visual Studio\VC98\。
● lib变量指定导入库文件的搜索目录。Ml.exe根据这个变量寻找includelib语句指定的导入库文件,Link.exe也根据这个变量寻找库文件的位置。设置lib变量带来的好处同上。
● path变量就不必多解释了。它只是使我们不必在键入命令时带长长的路径而已。
----------------------------------------------------------------------------------------------------------
这里记录学习的环境为Windows XP(装在VirtualBox虚拟机里)和Window10作为开发环境
Windows XP的masm32安装在C盘根目录下,同时把masm(16位编译器)的编译器也复制到C:\masm32\bin的目录下
VC 6.0安装企业版,nmake在VC的安装目录下。
设置环境变量C:\masm32\bin;C:\Program Files\Microsoft Visual Studio\VC98\Bin,
输入nmake /? 或 nmake /HELP命令,验证环境变量设置成功
win10环境采用VS2019作为编译器。
2.6.3 尝试编译第一个程序
按照上面的步骤安装完成后,下面来编译一个程序测试一下。打开一个文件浏览窗口,切换到源文件目录x:\Source,然后把本书所带光盘中的Chapter02\Test目录拷贝过来,现在有了一个需要编译的Test.asm文件在x:\Source\Test目录中。
打开一个MS-DOS窗口,并键入Var执行已建立的Var.bat,这时环境变量和路径已经设置好了,可以键入SET命令验证一下include和path等环境串是否正确,然后键入x:以及cd \Source\Test切换到要工作的目录中,并键入nmake,当屏幕上出现如下所示的正确的编译链接信息后,Test.exe就建立完成了。
Microsoft (R) Program Maintenance Utility Version 6.00.8168.0
Copyright (C) Microsoft Corp 1988-1998. All rights reserved. ml /c /coff Test.asm
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997. All rights reserved.
Assembling: Test.asm
rc Test.rc
Link /subsystem:windows Test.obj Test.res
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
执行一下Test.exe,会出现一个简单的对话框,表示一个可以执行的Win32程序正确生成了。万事大吉,这样就可以开始Win32汇编之旅了!
如果编译的过程中出现“can not open file xxx.inc”或“can not open file xxx.lib”的提示时,就要检查Var.bat文件中include或lib的路径是否正确。
下面的工作就是:编辑源程序→切换到MS-DOS窗口→键入nmake编译→运行生成的可执行文件→切换到文本编辑器修改源程序……如此循环往复调试程序。
测试本书所带光盘中的代码时,将光盘上的代码拷贝到硬盘上后,别忘了把拷贝过来的文件去掉“只读”属性,否则修改或编译时可能会因无法写文件而产生错误。另外,也不要忘了先校对本机的时钟,因为nmake工具根据当前时钟和文件时间对比来决定是否执行编译和链接的命令。
执行Var.bat设置环境变量的操作只需在每次刚打开MS-DOS窗口的时候执行一次就可以了。调试Win32汇编程序时常见的桌面如图2.5所示:屏幕上一般有个MS-DOS窗口来执行编译命令,有一个文本编辑器窗口修改源代码,同时使用MSDN等联机帮助文件,当然再开一个WinAmp窗口同时听一听MP3也不错!
图2.5 工作环境
-------------------------------------------------------------------------------------------------------------
windows XP环境编译:
在工作目录下创建chapter02目录,把源码Test目录拷贝到chapter02目录,编译Var.bat批处理文件,内容如下:
@echo off
set masm32Dir=C:\masm32
set vc6Dir="C:\Program Files\Microsoft Visual Studio\VC98"
set include=%masm32Dir%\include;%vc6Dir%\Include;%vc6Dir%\MFC\Include;%include%
set lib=%masm32Dir%\lib;%lib%
set path=%masm32Dir%\Bin;%masm32Dir%;%PATH%
set masm32Dir=
echo on
打开cmd命令行工具,切换到源码目录,先执行Var.bat批处理文件,再执行nmake命令,编译生成可执行文件Test.exe
生成Test.exe可执行文件,双击Test.exe运行:
Win10环境 VS2019编译,新建汇编项目工程后,添加Test.asm文件,输入源代码如下:
; Test.asm
; 编程环境测试代码
include Irvine32.inc ;这里用Irvine32.inc头文件
; 数据段
.const
szCaption db '恭喜',0
szText db '当您看到这个信息的时候,您已经可以编译Win32汇编程序了!',0
; 代码段
.code
start:
invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK
invoke ExitProcess,NULL
end start
添加ico,rc文件
编译运行:
到此WindowXP和Win10的开发环境都搭建好了。