WIN32汇编语言教程:第02章 准备编程环境 · 2.4 make工具的用法(1)
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工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,同样是用类似于批处理的方式——通过调用用户指定的语句来进行编译和链接。但是,批处理会执行全部命令将全部源文件编译,包括那些不必重新编译的源文件,而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所示。
表2.8 nmake的常用选项
选项 | 简介 |
/A | 不检测文件时间,强制更新所有文件 |
/B | 文件时间相等时也要更新文件 |
/D | make时显示文件新旧信息 |
/N | 显示make时要执行的命令,但并不真正执行 |
/P | 一个比较有用的选择,make时显示详细的信息 |
由于nmake的应用是基于文件时间的,当计算机的时钟不准确或文件拷贝到另一台计算机后文件时间有些偏差,那么可能文件的更新会不正确,这时最好用/A选项强制把所有文件更新一遍。在平时使用的时候,以makefile当做建立的描述文件名,那么仅键入不加参数的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的换行符是\,如例子中的第三行和第四行可以合并为:
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。