以前看书时经常遇到makefile,nmake这几个名词,然后随之而来的就是一大段莫名其妙的代码,把我看得云里雾里的。在图书馆和google上搜了半天,也只能找到一些零零星星的资料,把我一直郁闷得不行。最近因缘巧合,被我搞到了一份传说中的MASM6手册,终于揭开了NMAKE的庐山真面目。想到那些可能正遭受着同样苦难的同志以及那些看到E文就头晕的兄弟,所以就写了这篇文章。假如大家觉得有帮助的话,记得回复一下,当作鼓励!如果觉得很白痴,也请扔几个鸡蛋.本文是总结加翻译,对于一些关键词以及一些不是很确定的句子,保留了英文原版,然后再在括号里给出自己的理解以作参考。由于水平有限,加上使用NMAKE的经验尚浅,有不对的地方大家记得要指正唷。MASM6手册在AOGO(好像是)可以download,在我的BLOG上有到那的链接。
  关于NMAKE
  Microsoft Program Maintenance Utility,外号NMAKE,顾名思义,是用来管理程序的工具。其实说白了,就是一个解释程序。它处理一种叫做makefile的文件(以mak为后缀),解释里面的语句并执行相应的指令。我们编写makefile文件,按照规定的语法描述文件之间的依赖关系,以及与该依赖关系相关联的一系列操作。然后在调用NMAKE时,它会检查所有相关的文件,如果目标文件(target file,下文简称target,即依赖于其它文件的文件)的time stamp(就是文件最后一次被修改的时间,一个32位数,表示距离1980年以来经过的时间,以2秒为单位)小于依赖文件(dependent file,下文简称dependent,即被依赖的文件)的time stamp,NMAKE就执行与该依赖关系相关联的操作。请看下面这个例子:
  foo.exe : first.obj second.obj
  link first.obj,second.obj
  第一行定义了依赖关系,称为dependency line;第二行给出了与该依赖关系相关联的操作,称为command line。因为foo.exe由first.obj和second.obj连接而成,所以说foo.exe依赖于first.ogj和second.obj,即foo.exe为target,first.obj和second.obj为dependent。如果first.obj和second.obj中的任何一个被修改了(其time stamp更大),则调用link.exe,重新连接生成foo.exe。这就是NMAKE的执行逻辑。
  综上,NMAKE的核心就是这3个家伙——依赖关系,操作和判定逻辑(target.timestamp < dependent.timestamp,如果为true,就执行相应操作)。
  MAKEFILE的语法
  现在详细讨论一下makefile的语法。makefile就像一个玩具型的程序语言,麻雀虽小,但五脏具全。makefile的组成部分包括:描述语句(description block),inference rules(推导规则),宏和指令(directive)。描述语句就是dependent lines和command lines的组合;inference rules就是预先定义好的或用户自己定义的依赖关系和关联命令;宏就不用说了吧;指令就是内定的一些可以被NMAKE识别的控制命令,提供了很多有用的功能。
  另外,makefile中使用以下几个具有特殊意义的符号:
  ^ # \ ( ) { } ! @ - : ; $
  ^(caret):用于关闭某些字符所具有的特殊意义,使其只表示字面上的意义。例如:^#abc表示#abc这个字符串,而#abc则用于在makefile中加入注释,#在这里为注释标志,就像C++中的//。另外,在一行的末尾加上^,可以使行尾的回车换行符成为字串的一部分。
  #(number sign):为注释标志,NMAKE会忽略所有从#开始到下一个换行符之间的所有文本。这里要注意的是:在command lines中不能存在注释。因为对于command lines,NMAKE是将其整行传递给OS的。通常对于command lines的注释都是放在行与行之间。
  \(backslash):用于将两行合并为一行。将其放在行尾,NMAKE就会将行尾的回车换行符解释为空格(space)。
  %(percent symbol):表示其后的字符串为一文件名。用法较复杂,在讲dependent lines的时候再详细讨论。
  !(exclamation symbol):命令修饰符,在下面会有详细的讨论。
  @(at sign):命令修饰符,在下面会有详细的讨论。
  :(colon):用于dependent lines和inference rules中,用于分隔target和dependent。
  ;(semicolon):如果对于一个dependent line只有一条命令,则可以将该命令放在dependent line的后面,二者之间用“;”分隔。
  $(dolor sign):用于调用宏,在下面讲宏的时候再详细讨论。
  在makefile中还可以使用DOS通配符(wildcard)来描述文件:*和?。作用相信大家都很熟悉了,在此就不再浪费口水了。
  如果要将中间有空格或制表符的字符串作为整体对待,则应该用双引号将之括起来,例如,在指定一个中间有空格的长文件名的时候:
  “My Document”
  或在定义一个宏的时候:
   MYMACRO=”copy a:\foo.exe c:\”
  描述语句块(Description Blocks)
   描述语句块为makefile主体的基本组成单元,其典型结构如下:
   target : dependents
   commands block
  Dependent Line
   每一个描述语句块中只有一个dependent line,其定义了一个依赖关系。该行的开头不能有任何空白(空格或制表符)。冒号两边的target和dependent都可以有多个,之间以空格分隔。NMAKE在分析makefile时首先会从头到尾扫描每一个dependent line,然后根据依赖关系建立起一棵依赖关系树(dependent tree)。例如对于依赖关系:
   foo.exe : first.obj second.obj
   first.obj : first.cpp
   second.obj : second.cpp
  则在其依赖关系树中,foo.exe为first.obj和second.obj的父亲,而first.obj则是first.cpp的父亲,second.obj是second.cpp的父亲。如果second.cpp被更新了,则second.obj会被重新构造,从而导致foo.exe被重新构造。NMAKE就是这样由下而上地对整棵树中的结点进行评估的。
   虽然makefile中可以有很多的dependent lines,但NMAKE只会构造出现在它的命令行中的targets,或者,如果命令行中没有给出targets,就构造第一个dependent line中的第一个target。其他所有无关的targets都不会被构造。例如:
   foo1.exe foo2.exe : first.obj
   first.obj : first.cpp
   second.obj : second.cpp
  假设上面的第一行语句为makefile中出现的第一个dependent line,且命令行中没有给出target。当first.cpp被更新后,first.obj和foo1.exe都会被重新构造,而foo2.exe和second.obj则不会。
   当在一个dependent line中出现多个target时,例如:
   boy.exe girl.exe : first.obj
   echo Hello
   该语句相当于:
   boy.exe : first.obj
   echo Hello
   girl.exe : first.obj
   echo Hello
  (注:echo是一条控制台命令,用于在STDOUT上显示一行信息)
   同一个target也可以出现在多个dependent lines中。在这种情况下,如果只有一个dependent line后跟有command line,则它们会被合并为一个描述语句块,例如:
   foo.exe : first.obj
   echo Building foo.exe…
   …
   foo.exe : second.obj
  NMAKE会将其处理为:
   foo.exe : first.obj second.obj
   echo Building foo.exe…
   如果每一个dependent line后都有command line,则它们会被作为两个描述语句块处理。
  如果在dependent line中使用双冒号(::)来分隔target和dependent,并且同一个target出现在多个描述语句块中,此时,NMAKE将会匹配最合适的语句块,以构造该target。
  例如:
   target.lib :: one.asm two.asm three.asm
  ML one.asm two.asm three.asm
  LIB target -+one.obj -+two.obj -+three.obj;
  target.lib :: four.c five.c
  CL /c four.c five.c
  LIB target -+four.obj -+five.obj;
  Target.lib同时出现在两个描述语句块中,此时,NMAKE在处理该makefile时,将会选择其中一个描述语句块中的命令来执行。如果任何asm文件被更新了,NMAKE就调用ML重新编译之,然后再调用LIB(但CL以及之后的命令都不会被调用);类似地,如果任何C文件被更新了,NMAKE就会调用CL。
   在通常情况下,target和dependent都是文件名。NMAKE会首先在当前目录下搜索dependent,如果没有找到,就到用户指定的目录下搜索。指定搜索路径的语法如下:
   {directory1;directory2;…}dependent
  搜索路径放在{}之中,如果有多个,就用“;”分开。注意,在各个语法成分之间是不能有空白的。
   Target和dependent也可以不是一个文件,而是一个标号(label)。这时,就称之为pseudotarget(伪文件)。Pseudotarget的名字不能与当前目录下的任何文件名相同。一个pseudotarget如果要作为dependent,那么它必须要作为target出现在某个dependent line中。当使用pseudotarget作为target时,与之关联的commands block一定会被执行,同时NMAKE会赋予它一个假想的time stamp。该time stamp等于它的dependents中最大的time stamp,或者,如果它没有dependent,就等于当前时间。该假想的time stamp在pseudotarget作为dependent时会被用来进行有效性评估。这个特性最大的好处就是,你可以让NMAKE构造多个target,而不用将每个target都在NMAKE的命令行中列出来,例如:
   all : setenv project1.exe project2.exe
  project1.exe : project1.obj
  LINK project1;
  project2.exe : project2.obj
  LINK project2;
  setenv :
  set LIB=\project\lib
  上例中有两个pseudotarget,一个是all,另一个是setenv。首先是setenv被评估,其作用是设置环境变量LIB,然后project1.exe和project2.exe依次被更新.
  Commands Block
   第二行开始到下一个dependent line之间为commands block,其给出了当dependents中的任何一个的time stamp大于target时,需要执行的指令序列(commadns block也可以为空,此时,NMAKE什么也不干)。command line必须以空白开头(刚好与dependent line相反,NMAKE就是通过该特征来分辨二者的),并且在dependent line和commands block中的第一条语句之间不能有空白行(就是除了一个换行符,什么也没有的行。所以只有一个空格或制表符的行是合法的,此时NMAKE将其解释为一个null command),但在command lines之间可以有空白行。Commands block中的每一条命令可以是在控制台中合法的任何命令。事实上大可将commands block当成一个由控制台命令序列组成的批处理文件。
   此外,对commands block中的命令,还可以在其前面添加一个或多个所谓的命令修饰符(command modifier),以实现对命令的一些额外的控制。命令修饰符有以下3种:
  1) @command
  消除该命令的所有到STDOUT的输出。
  2) –[number]command
  关掉对该命令返回值的检测。在默认的情况下,如果一条命令返回非0值,则NMAKE将会停止执行。但如果在命令前加上一“-”,则NMAKE将会忽略该命令的返回值。如果“-”紧接着一个整数,则NMAKE会忽略掉任何大于该整数的返回值。
  3) !command
  如果该命令的执行对象为$**或$?(这两个都是预定义的宏,前者表示相应的dependent line中所有的dependent,后者表示所有比target具有更大的time stamp的dependent),则该“!”修饰符将会使该命令施行于这两个宏所描述的每一个独立的文件上。
   NMAKE还提供了一些语法可以在commands block中表示相应的dependent line中第一个dependent的文件名组成。例如:
   foo.exe : c:\sample\first.obj c:\sample\second.obj
   link %s
  NMAKE将“link %s”解释为:
   link c:\sample\first.obj
  如果将命令改为“link %|pfF.exe”,则NMAKE将之解释为:
   link c:\sample\first.exe
  %s表示全文件名,%|[part]F表示文件名中的某个部分,part可以是下列字符中的一个或多个,如果part为空,%|F与%s的意思相同:
  1) d:盘符;
  2) p:路径;
  3) f:文件基本名;
  4) e:文件扩展名;
Inference Rules(推导规则)
   Inference rules(下文简称IR)是一个模板,它用于决定如何从一个具有某种扩展名的文件构造出一个具有另一种扩展名的文件。NMAKE通过IR来确定用来更新target的命令以及推导target的dependents。IR的好处在于它满足了像我这样的懒人的需要。只要提供了正确的IR,则描述语句块就可以极大地化简。请看下面的例子:
   foo.obj :
  上面的语句将会运作得很好。是不是觉得很吃惊呢?事实上,NMAKE在处理该语句的时候,它首先在当前目录下搜索基本名为foo的文件(假设当前目录下有一个foo.c文件)。然后它查找一个后缀列表(suffix list),里面的每一项包含了从一种类型的文件构造另一种类型的文件需要调用的命令和参数的相关信息。在NMAKE预定义的列表中,foo.c到foo.obj的构造命令为CL。最后NMAKE调用CL,编译foo.c。呵呵,这么一长串的操作一条简单的语句就搞定了,是不是很方便呢!
   当出现下列情况之一时,NMAKE就会尝试使用IR:
  l NMAKE遇到一个没有任何命令的描述语句块。此时NMAKE就会搜索后缀列表,试图找到一个匹配的命令来构造target。
  l 无法找到某个dependent,并且该dependent没有作为target出现在其它dependent line中(即它不是一个pseudotarget)。此时NMAKE就会搜索给定的目录以及后缀列表,试图找到一个IR来构造出该dependent。
  l 一个target没有dependent,并且描述语句块中没有给出指令。此时NMAKE就会试图找出一个IR来构造出该target。
  l 一个target在NMAKE的命令行中给出,但在makefile里没有该target的相关信息(或根本就没有makefile)。此时NMAKE就会试图找出一个IR来构造出该target。
  定义一个IR的语法如下:
   [{frompath}].fromext[{topath}].toext;
   commands
  注意,各语法元素之间不能有任何空格。Dependent的后缀名在fromext中给出,target的后缀名在toext中给出。Frompath和topath是可选的,分别给出了搜索的路径。在每个IR的定义中只能分别为每一个后缀名给出一个搜索路径。如果想要指定多个搜索路径,就必须定义多个IR。并且,如果你为一个后缀指定了搜索路径,那么你也必须为另一个后缀指定搜索路径。即是说,fromext和topath只要有一个存在,则另一个也必须存在。你可以使用{.}或{}来表示当前目录。
  另外,要注意的是,如果你在IR中指定了搜索路径,则在dependent lien中也必须指定同样的路径,否则IR将不会应用于dependent line上,例如:
   {..\proj}.exe{..\proj}.obj:
  该IR不会用于下列语句上:
  project1.exe : project1.obj
  但会用于下列语句上:
  {..\proj}project1.exe : {..\proj}project1.obj
  NMAKE本身提供了一个预定义的后缀列表,内容如下:
   Rule Command Default Action
   .asm.exe $(AS)$(AFLAGS) $*.asm ML $*.ASM
  .asm.obj $(AS)$(AFLAGS) /c $*.asm ML /c $*.ASM
  .c.exe $(CC)$(CFLAGS) $*.c CL $*.C
  .c.obj $(CC)$(CFLAGS) /c $*.c CL /c $*.C
  .cpp.exe $(CPP)$(CPPFLAGS) $*.cpp CL $*.CPP
   .cpp.obj $(CPP)$(CPPFLAGS) /c $*.cpp CL /c $*.CPP
  .cxx.exe $(CXX) $(CXXFLAGS) $*.cxx CL $*.CXX
  .cxx.obj $(CXX) $(CXXFLAGS) /c $*.cxx CL /c $*.CXX
  .bas.obj $(BC) $(BFLAGS) $*.bas; BC $*.BAS;
  .cbl.exe $(COBOL) $(COBFLAGS) $*.cbl, $*.exe; COBOL $*.CBL, $*.EXE;
  .cbl.obj $(COBOL) $(COBFLAGS) $*.cbl; COBOL $*.CBL;
  .for.exe $(FOR) $(FFLAGS) $*.for FL $*.FOR
  .for.obj $(FOR) /c $(FFLAGS) $*.for FL /c $*.FOR
  .pas.exe $(PASCAL) $(PFLAGS) $*.pas PL $*.PAS
  .pas.obj $(PASCAL) /c $(PFLAGS) $*.pas PL /c $*.PAS
  .rc.res $(RC) $(RFLAGS) /r $* RC /r $*
   在上表中,类似AFLAG和CFLAG这种被包含在括号里面的是未定义的宏,通过在makefile中对这些宏给出定义,可以为这些命令指定编译器和参数。例如:
   $(AS)$(AFLAGS) $*.asm
  AS宏用于指定编译器,NMAKE中默认为ML;AFLAGS宏用于给出编译器参数,NMAKE将之留给用户定义,默认为空。所以默认的操作为:
  ML $*.asm
  这里可以看到将宏展开的语法,就是将宏的名字用圆括号括起来,然后在前面加上一个美元符号。另外需要说明的是,”$*”是NMAKE预定义的一个特殊的宏,其等于target的路径加上target的基本名。
  宏(MARCRO)
   这个相信大家都十分熟悉了。在makefile中通过使用宏将可以获得很大的灵活性。下面就是在makefile中定义宏的语法:
   macroname=string
  在makefile中,macroname是宏的名字,其可以是任何字母,数字和下划线的组合,最多可以有1024个字符。另外要注意的是,macroname是大小写敏感的。string是宏的定义体,可以有高达65510个字符。任何包含0个字符或只包含空白的字符串都被视为空字串(null string),此时,该宏也被视为NULL,任何其出现的地方,都会被替换为空白。
   在使用宏时,还应知道以下几个具有特殊意义的符号:
  l # 用于注释,例如:
  command=ML # compile asm file
  l \ 将宏定义分作多行来写,例如:
  LINKCMD = link myapp
  another, , NUL, mylib, myapp
  “\”后面的回车换行符会被空格替换,上面两行相当于:
  LINKCMD = link myapp another, , NUL, mylib, myapp
  l $ 将宏展开,用法在后面介绍。
  l ^ 如果要在宏中包含以上符号,但又不使用它们的特殊语义,则可以这样:
  dir=c:\windows^
  此时,dir相当于字符串”c:\windows\”。
   以下是一些语法上的细节:
  1) 在定义宏时,宏名字的第一个字符必须是该行的第一个字符;
  2) 每行只能定义一个宏;
  3) 在”=”两边可以有空格,但它们都会被忽略;
  4) 在宏定义体中可以有空格,它们都会被视为宏的一部分;
  除了可以在makefile中定义宏之外,宏定义也可以出现在NMAKE命令行中。此时,如果在宏定义中有任何空白,则必须用双引号将之括起来,例如:
  NMAKE "LINKCMD = LINK /MAP"
  NMAKE LINKCMD="LINK /MAP"
  而像下面这样则是不允许的(等号两边有空格):
   NMAKE LINKCMD = "LINK /MAP"
   使用宏的语法如下(注意,整个语句中不能有任何空格):
   $(macroname)
   NMAKE会将整个语句用宏替换掉。如果宏未定义,NMAKE会用空白替换之,不会产生任何错误。如果宏的名字只有一个字符,则括号可以省略,例如:$L和$(L)是等价的。
   NMAKE为宏的使用还提供了一个很有用的特性,那就是substitution(子替换)。即是在展开宏的时候,你还可以指明将展开的宏中的某部分文本用另外的文本替换掉。例如:
   SOURCE=one.c two.c
  foo.exe : $(SOURCE:.c=.obj)
   LINK $**;
  展开来就是这样:
  SOURCE=one.c two.c
  foo.exe : one.obj two.obj
   LINK one.obj two.obj;
  语句$(SOURCE:.c=.obj)表示将SOURCE中出现的所有”.c”替换为”.obj”。
  由以上的例子可以看出,substitution的语法如下(注意,没有空格):
  $(macroname:str1=str2)
   此外,NMAKE还提供了4组预定义的宏,它们分别是文件名宏,递归宏,命令宏和参数宏。它们都可以被重新定义,但可能会引起一些不必要的麻烦,因为它们被广泛使用。正所谓“动一发而牵全身”,一个小小的改动,甚至有可能会影响到太阳黑子的运动(蝴蝶效应),这就是使用宏的最大的弊端。
  文件名宏
  在commands block中使用,以表示特定的文件名,包括:
  1) $@ 用来表示相关联的dependent line中第一个target的全名(包括路径)。
  2) $$@ 同上,但只能用在dependent line中。
  3) $* target的路径加基本名。
  4) $** 相应的dependent line中的所有dependent。
  5) $? 相应的dependent line中的所有time stamp大于target的dependent。
  6) $< 同上,但只能用在IR中。
  下面是一个例子:
  DIR = c:\objects
  $(DIR)\a.obj : a.obj
  COPY a.obj $@
   最后一句展开来就相当于:copy a.obj c:\objects\a.obj
   另外,在使用以上这些宏的时候,还可以通过以下的字符来提取文件名中的某一个部分:
   D 路径
   B 基本名
   F 基本名加扩展名
   R 路径加基本名
   例如:如果$@表示c:\objects\a.object,则
   $(@D) c:\objects
   $(@B) a
   $(@F) a.obj
   $(@R) c:\objects\a
  递归宏
   有3个,它们都是用来在makefile中方便地进行NMAKE的递归调用,它们分别是:
  1) MAKE
  表示运行当前makefile的NMAKE程序的名字。例如,如果你在控制台用以下语句运行makefile:
  NMAKE her.mak
  则MAKE就等于NMAKE。
  但如果你将NMAKE.EXE改名为FUCK.EXE,那么你运行makefile的命令就应该改为:
  FUCK her.mak
  此时,MAKE就等于FUCK。
  2) MAKEDIR
  表示你调用NMAKE时所在的目录。
  3) MAKEFLAGS
  表示你运行当前makefile时使用的NMAKE参数。
   这几个宏在build程序的不同版本时特别有用,例如:
   all : vers1 vers2
  vers1 :
  cd \vers1
  $(MAKE)
  cd ..
  vers2 :
  cd \vers2
  $(MAKE) /F vers2.mak
  cd ..
   NMAKE会分别在.\vers1和.\vers2目录下运行vers1.mak和vers2.mak。
  命令宏和参数宏
   命令宏表示Microsoft的编译程序(真的很会做生意,任何时候都不忘自己的产品),而参数宏则是表示传递给这些编译器的参数,在默认情况下,参数宏都是未定义的。当然,你可以重新定义它们,让它们表示Boland的编译程序和参数。
   命令宏 对应的参数宏
  1) AS ml,M的汇编编译器。 AFLAGS
  2) BC bc,M的BASIC编译器。 BFLAGS
  3) CC cl,M的C编译器。 CFLAGS
  4) COBOL cobol,M的COBOL编译器。 COBFLAGS
  5) CPP cl,M的C++编译器。 CPPFLAGS
  6) CXX cl,M的C++编译器。 CXXFLAGS
  7) FOR fl,M的FORTRAN编译器。 FFLAGS
  8) PASCAL pl,M的PASCAL编译器。 PFLAGS
  9) RC rc,M的资源编译器。 RFLAGS  

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mirror_hc/archive/2008/03/26/2221117.aspx

posted @ 2009-07-15 07:36 卡洛shll 阅读(402) | 评论 (0)编辑 收藏

1 makefile入门
Windows CE的构建系统大量使用了Nmake工具和makfile。在大多数微软的软件和驱动开发包中都会包含Nmake工具。因此,这里有必要介绍一下makefile和Nmake工具。
1.1 makefile简介
对于许多Windows下的程序员来说,makefile可能还是个陌生的名词。因为Windows下的许多集成开发环境(例如:Microsoft Visual Studio和Borland C++ Builder等)可以帮助开发人员完成makefile需要完成的功能。通常只需要在集成开发环境中按个按钮,工具就可自动帮助我们编译、链接整个项目。想象如果没有了集成开发环境,那么就需要有另外一种方式来管理对项目的构建。
简单的来说,makefile负责帮助开发人员简化代码的编译、链接等构建工作。对于只包含几个文件的简单的项目,开发人员完全可以通过手动控制编译器、链接器来完成对项目的构建。但是想象一下对于一个拥有几百个、甚至几千个文件的大型项目,如果每次构建都是通过手动完成,那消耗的工作量和复杂程度是不可想象的。在这种情况下,makefile就有了它的用武之地。
makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个自动化脚本一样,其中也可以执行操作系统的命令。
makefile带来的最大的好处是“自动化构建”。写好makefile之后,在编译的时候只需要一个命令,整个工程完全自动编译、链接,极大的提高了软件开发的效率。
makefile本质上只是一个文本文件,本身并不能运行。在运行makefile的时候还是需要外部程序来对makefile进行解释执行。NMake.exe就是用来解析并执行makefile的工具。
当用户输入nmake命令后,首先nmake会读取makefile,然后解析makefile,并根据makefile的规则来确定要编译哪些代码。然后nmake会调用编译器、链接器等一些开发工具,完成对代码的编译链接。最终会生成可执行文件。

图:makefile的工作流程
值得一提的是makefile既不是Windows CE特有的工具也不是微软的发明创造。makefile是一种通用的自动化构建手段。在UNIX/Linux平台下有着广泛而众多的应用。许多开发工具都会提供NMake类似的工具。比如:Delphi的make,Visual C++的nmake,Linux下GNU的make等等。
1.2 makefile的编
写规则
makefile是由一个个推导和规则构成的,在NMake中这也被叫做描述块(Description Blocks)。一个最基本的推导规则的语法如下。

targets... : dependents...
     commands...

targets也就是一个目标文件,可以是Object File,也可以是可执行文件。还可以是一个标签(Label)。targets必须在一行的顶格写,前面不能有空格。
dependents就是要生成target所需要的文件或是目标依赖项。dependents与targets之间用冒号间隔。一个targets可以有多个dependents。
command也就是NMake需要执行的命令。其中commands可以是任意的Windows命令行命令。
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于dependents中的文件,其生成规则定义在command中。也就是说,dependents中如果有一个以上的文件比targets文件要新的话,command所定义的命令就会被执行。这就是makefile的规则。也就是Makefile中最核心的内容。
掌握了makefile最核心的内容,就可以尝试编写第一个makefile了。但是仅仅知道了这一点还远远不够。makefile还有很多细节的内容,下面会一点一点地介绍。在此之前,先看一个可以实际运行的makefile。以便读者对makefile有个感性的认识。
1.3 一个实际可以运行的makefile
在%_WINCEROOT%\PBWorkspaces\MyPlatform\下新建立一个目录hello,然后在hello目录下创建hello.cpp,内容如下:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance,
                      HINSTANCE hPrevInstance,
                      LPTSTR      lpCmdLine,
                      int        nCmdShow)
{
MessageBox(NULL, L"Hello", L"bb", 0);
}

本部分内容的目的就是使用NMake工具来把Hello.cpp构建为Hello.exe可执行文件。
为此,在hello目录下再创建一个文本文件,名为makefile(没有扩展名)。然后用文本编辑器在makefile中输入如下内容:

#This is a demo makefile

hello.exe: hello.obj
     @echo linking...
     link -MACHINE:x86 -NODEFAULTLIB -subsystem:windowsce,5.00 -entry:WinMainCRTStartup -LIBPATH:E:\WINCE500\PBWorkspaces\MyPlatform\projroot\cesysgen\sdk\lib\x86\retail hello.obj coredll.lib corelibc.lib


hello.obj: hello.cpp
     @echo compiling...
     cl -nologo -c -I. -IE:\WINCE500\public\common\sdk\inc -DUNICODE -D_UNICODE -DUNDER_CE=500 -D_WIN32_WCE=500 -DWIN32 -DSTRICT -Dx86 -D_X86_ -DINTERNATIONAL -DL0804 -DINTLMSG_CODEPAGE=1252 hello.cpp

clean:
     del hello.obj, hello.exe

第一行是注释,在makefile中,用#表示该行内容是注释。
按照上文介绍的推导规则,这段makefile定义了两个依赖规则。hello.exe依赖于hello.obj,hello.obj又依赖于hello.cpp。
要从hello.cpp生成hello.obj,需要经过编译过程,执行编译命令。上文makefile代码中定义了两个命令,一个是操作系统的内部命令echo,作用是输出一个字符串,另外一个是调用C++编译器cl.exe,并用-c指示它只进行编译工作而不进行链接。对于从hello.obj生成hello.exe的过程也是一样的。中间调用了链接器link.exe把几个库文件链接成hello.exe。
为了简单起见,用到的路径都直接采用了写死的绝对路径。
从“开始”  “程序”  “Microsoft Windows CE 5.0” 菜单打开Windows CE的控制台。然后cd到hello目录,输入nmake命令,控制台的输出结果如下:

E:\WINCE500\PBWorkspaces\MyPlatform\hello>nmake
compiling...
Windows CE Version (Release) (Built on Mar   1 2004 21:46:39)
cl -nologo -c -I. -IE:\WINCE500\public\common\sdk\inc -DUNICODE -D_UNICODE -DUNDER_CE=500 -D_WIN32_WCE=500 -DWIN32 -DSTRICT -Dx86 -D_X86_ -DINTERNATIONAL -DL0804 -DINTLMSG_CODEPAGE=1252 hello.cpp
hello.cpp
linking...
link -MACHINE:x86 -NODEFAULTLIB -subsystem:windowsce,5.00 -entry:WinMainCRTStartup -LIBPATH:E:\WINCE500\PBWorkspaces\MyPlatform\projroot\cesysgen\sdk\lib\x86\retail hello.obj coredll.lib corelibc.lib
Microsoft (R) Incremental Linker Version 7.10.4017
Copyright (C) Microsoft Corporation.   All rights reserved.

这是使用dir命令查看hello目录,可以看到多了hello.obj和hello.cpp两个文件。这说明nmake已经帮我们生成了目标文件。
对于这个makefile,还有最后要介绍的一点。makefile中的clean是一个标签,通常所有的makefile中都会有clean这样一个标签,用来清理生成的文件,以便重新编译。为了调用这个标签可以在命令行下输入如下指令

nmake clean

这样,nmake就会帮我们调用系统的del命令,删除构建时生成的hello.obj和hello.exe文件了。
1.4 使用变量
让我们再回头来看看上一节中的示例makefile。如果我们希望把生成的文件由hello.exe改变为nihao.exe,那么仅仅这一丁点改动,在makefile中需要修改的地方就多达五处。这对于makefile的维护非常不方便。如果能有一种类似于C语言中宏或者变量的机制,就可以解决这个问题了。
为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。
比如,我们声明一个变量,叫TARGETNAME,在makefile一开始就这样定义:

TARGETNAME = hello

于是,我们就可以很方便地在我们的makefile中以“$(TARGETNAME)”的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:

TARGETNAME = hello
SOURCES = hello.cpp
TARGETLIBS = $(TARGETNAME).obj coredll.lib corelibc.lib

LINK = link

CPPFLAGS = -nologo -c -I. -IE:\WINCE500\public\common\sdk\inc -DUNICODE -D_UNICODE -DUNDER_CE=500 -D_WIN32_WCE=500 -DWIN32 -DSTRICT -Dx86 -D_X86_ -DINTERNATIONAL -DL0804 -DINTLMSG_CODEPAGE=1252

LFLAGS = -MACHINE:x86 -NODEFAULTLIB -subsystem:windowsce,5.00 -entry:WinMainCRTStartup -LIBPATH:E:\WINCE500\PBWorkspaces\MyPlatform\projroot\cesysgen\sdk\lib\x86\retail

$(TARGETNAME).exe: $(TARGETNAME).obj
     @echo linking...
     $(LINK) $(LFLAGS) $(TARGETLIBS)

$(TARGETNAME).obj: $(SOURCES)
     @echo compiling...
     $(CPP) $(CPPFLAGS) $(SOURCES)

clean:
     del *.obj, *.exe

在这一版本的makefile中,我们在makefile的最开始定义了六个变量。TARGETNAME表示要生成的文件名,SOURCES表示源代码列表。TARGETLIBS表示要链接的库列表。LINK表示链接器的名称。CPPFLAGS和LFLAGS分别表示编译器和链接器的命令行参数。
有了这些变量定义之后,编译和链接的命令就可以写的非常简洁了,例如编译源代码的命令就可以写成:$(CPP) $(CPPFLAGS) $(SOURCES)。nmake工具会自动用变量替换这些标记,最终的结果与第一个版本还是一样的。
如果读者细心的话,可以发现我们在makefile中并没有定义CPP这个变量,但是在makefile中我们依然使用了CPP变量。这又是为什么呢?其实nmake工具默认会设置一些变量的值,对于C++编译器,nmake会默认定义CPP变量,并把它的值赋为cl,因此,在使用的时候,makefile代码中就不需要重复定义了。
1.5 使用预处理
经过上一节的改动,namefile已经有了很大的灵活性。但是,依然达不到尽善尽美的地步。如果我们要把EXE文件的入口点从WinMainCRTStartup()函数修改到WinMain(),那么依然需要修改makefile。
使用预处理可以很好的解决上面的问题。在makefile中,NMake工具允许使用预处理机制来完成如下功能:
 按条件处理makefile
 显示错误信息
 包含其它的makefile
 打开/关闭某些nmake工具的命令行开关
预处理指令以“!”开头,必须出现在每行的最开始。最常用的预处理指令是条件处理。Nmake支持如下的条件预处理指令:

!IF
!IFDEF
!IFNDEF
!ELSE
!ELSEIF
!ELSEIFDEF
!ELSEIFNDEF
!ENDIF

它们的用法与C语言的预处理宏类似,相信读者可以很容易的理解它们的含义。
使用预处理机制来修改上一个版本的makefile,得到的新makefile如下所示:

TARGETNAME = hello
SOURCES = hello.cpp
EXEENTRY = WinMain
TARGETLIBS = $(TARGETNAME).obj coredll.lib corelibc.lib

LINK = link

!IFDEF EXEENTRY
!     MESSAGE EXEENTRY: $(EXEENTRY)
EXEENTRYOPTION=-entry:$(EXEENTRY)
!ELSE
EXEENTRYOPTION=-entry:WinMainCRTStartup
!ENDIF

CPPFLAGS = -nologo -c -I. -IE:\WINCE500\public\common\sdk\inc -DUNICODE -D_UNICODE -DUNDER_CE=500 -D_WIN32_WCE=500 -DWIN32 -DSTRICT -Dx86 -D_X86_ -DINTERNATIONAL -DL0804 -DINTLMSG_CODEPAGE=1252
LFLAGS = $(EXEENTRYOPTION) -MACHINE:x86 -NODEFAULTLIB -subsystem:windowsce,5.00 -LIBPATH:E:\WINCE500\PBWorkspaces\MyPlatform\projroot\cesysgen\sdk\lib\x86\retail


$(TARGETNAME).exe: $(TARGETNAME).obj
     @echo linking...
     $(LINK) $(LFLAGS) $(TARGETLIBS)

$(TARGETNAME).obj: $(SOURCES)
     @echo compiling...
     $(CPP) $(CPPFLAGS) $(SOURCES)

clean:
     del *.obj, *.exe

在这个版本中,主要的改动是新增加了一个变量EXEENTRY,并且增加了对于这个变量的预处理判断。如果用户定义了EXEENTRY变量,则把变量EXEENTRYOPTION的值设置成-entry:EXEENTRY,否则就设置成默认的CRT入口函数-entry:WinMainCRTStartup。修改相应的LFLAGS,把EXEENTRYOPTION加到LFLAGS中,修改就生效了。
这样,其实新增加的EXEENTRY是一个可选的变量,如果用户没有定义这个变量的值,构建也不会出错。使用预处理技术对于维护makefile,保持它的向下兼容非常有效。
注意,代码中出现的!MESSAGE也是一个宏,用来向标准输出stdout输出一个字符串。
1.6 包含其它文件
经过上面的修改,makefile中的有些模块已经非常通用了,对于每个项目都建立一个makefile也是比较复杂的。为了增强代码的重用性,可以考虑把makefile代码中通用的部分抽取出来,放在一个独立的文件中。以便在多个项目中公用。
预处理的另外一个作用是包含其它makefile文件。语法是:

! INCLUDE [<] 文件名 [>]

使用这个功能,可以实现把makefile拆分的目的。
这次,我们把makefile拆分成三个文件,名字分别叫:sources、makefile和makefile.def,都放在hello目录中。三个文件的内容分别如下:
sources文件的内容:

# This is a demo sources file

TARGETNAME =     hello
SOURCES =        hello.cpp
EXEENTRY =       WinMain
TARGETLIBS =     coredll.lib \
                 corelibc.lib

makefile文件的内容:

!    INCLUDE makefile.def

makefile.inc文件的内容:

!    INCLUDE .\sources

TARGETLIBS =     $(TARGETLIBS) \
                 $(TARGETNAME).obj

LINK = link

!IFDEF EXEENTRY
!     MESSAGE EXEENTRY: $(EXEENTRY)
EXEENTRYOPTION=-entry:$(EXEENTRY)
!ELSE
EXEENTRYOPTION=-entry:WinMainCRTStartup
!ENDIF

CPPFLAGS = -nologo -c -I. -IE:\WINCE500\public\common\sdk\inc -DUNICODE -D_UNICODE -DUNDER_CE=500 -D_WIN32_WCE=500 -DWIN32 -DSTRICT -Dx86 -D_X86_ -DINTERNATIONAL -DL0804 -DINTLMSG_CODEPAGE=1252
LFLAGS = $(EXEENTRYOPTION) -MACHINE:x86 -NODEFAULTLIB -subsystem:windowsce,5.00 -LIBPATH:E:\WINCE500\PBWorkspaces\MyPlatform\projroot\cesysgen\sdk\lib\x86\retail

$(TARGETNAME).exe: $(TARGETNAME).obj
     @echo linking...
     $(LINK) $(LFLAGS) $(TARGETLIBS)

$(TARGETNAME).obj: $(SOURCES)
     @echo compiling...
     $(CPP) $(CPPFLAGS) $(SOURCES)

clean:
     del *.obj, *.exe

在sources文件中我们只存放一些变量的定义,如果需要更改某些设置,只需要改动sources文件就好了。在makefile中,仅有简单的一行,把makefile.def文件导入进来。在makefile.def文件中包含所有关联推导和变量使用,并且还会把sources文件导入进来。
在控制台下输入nmake和nmake clean,同样可以顺利地对hello.exe进行构建和清除。
好了,sources、makefile和makefile.def。至此为止,一个具体而微的Windows CE构建系统就这么被我们给模拟出来了。Windows CE构建系统中的DIRS文件是build.exe在进行处理,NMake工具不会处理DIRS。
有了这些知识,读者在学习Windows CE的构建系统时,遇到makefile相关的内容应该不会再手足无措了。但是对于makefile本身的功能和作用而言,我们才刚刚开始

posted @ 2009-07-15 07:35 卡洛shll 阅读(458) | 评论 (0)编辑 收藏

AnimateWindow  函数功能:该函数能在显示与隐藏窗口时能产生特殊的效果。有两种类型的动画效果:滚动动画和滑动动画。
  函数原型:BOOL AnimateWindow(HWND hWnd,DWORD dwTime,DWORD dwFlags);
  参数:
  hWnd:指定产生动画的窗口的句柄。
  dwTime:指明动画持续的时间(以微秒计),完成一个动画的标准时间为200微秒。
  dwFags:指定动画类型。这个参数可以是一个或多个下列标志的组合。标志描述:
  AW_SLIDE:使用滑动类型。缺省则为滚动动画类型。当使用AW_CENTER标志时,这个标志就被忽略。
  AW_ACTIVE:激活窗口。在使用了AW_HIDE标志后不要使用这个标志。
  AW_BLEND:使用淡出效果。只有当hWnd为顶层窗口的时候才可以使用此标志。
  AW_HIDE:隐藏窗口,缺省则显示窗口。
  AW_CENTER:若使用了AW_HIDE标志,则使窗口向内重叠;若未使用AW_HIDE标志,则使窗口向外扩展。
  AW_HOR_POSITIVE:自左向右显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略。
  AW_VER_POSITIVE:自顶向下显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略。
  AW_VER_NEGATIVE:自下向上显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略。
  AW_HOR_NEGATIVE:自右向左显示窗口。该标志可以在滚动动画和滑动动画中使用。当使用AW_CENTER标志时,该标志将被忽略。
  返回值:如果函数成功,返回值为非零;如果函数失败,返回值为零。在下列情况下函数将失败:
  窗口使用了窗口边界;窗口已经可见仍要显示窗口;窗口已经隐藏仍要隐藏窗口。若想获得更多错误信息,请调用GetLastError函数。
  备注:可以将AW_HOR_POSITIVE或AW_HOR_NEGTVE与AW_VER_POSITVE或AW_VER_NEGATIVE组合来激活一个窗口。
  可能需要在该窗口的窗口过程和它的子窗口的窗口过程中处理WM_PRINT或WM_PRINTCLIENT消息。对话框,控制,及共用控制已处理WM_PRINTCLIENT消息,缺省窗口过程也已处理WM_PRINT消息。

posted @ 2009-07-14 07:43 卡洛shll 阅读(381) | 评论 (0)编辑 收藏

    一般的做法是在 C**App::InitInstance()中,修改成这样:
{
//...
m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
m_pMainWnd->UpdateWindow();
//...
}
或者,还在 CMainFrame::PreCreateWindow(CREATESTRUCT& cs)中,添加:
{
//...
cs.style |= WS_MAXIMIZE;
//...
}

这种做法能产生窗口最大化,但效果是显示的时候窗口从普通大小"闪"到最大化。还有的做法,是先将窗口隐藏,然后再最大化。那么怎样使窗口正常一开始出现就最大化?看看下面的流程,从 C**App::InitInstance()中的ProcessShellCommand(...)开始:
{
//...
//ProcessShellCommand中第一次显示了窗口
if (!ProcessShellCommand(cmdInfo))
   return FALSE;
m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
m_pMainWnd->UpdateWindow();
//...
}


->CWinApp::ProcessShellCommand
->AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL)
//如果你自己处理了ID_FILE_NEW要调用CWinApp::OnFileNew()
->CWinApp::OnFileNew()
->CDocManager::OnFileNew()
->CSingleDocTemplate::OpenDocumentFile //当前文档模板初始化
->CSingleDocTemplate::CreateNewDocument //创建文档
//加载资源并创建主窗口(顺便创建视图),但没显示
->CSingleDocTemplate::CreateNewFrame
->CFrameWnd::InitialUpdateFrame
{
//...
int nCmdShow = -1;      // default
CWinApp* pApp = AfxGetApp();
if (pApp != NULL && pApp->m_pMainWnd == this)
{
   nCmdShow = pApp->m_nCmdShow; // use the parameter from WinMain
   pApp->m_nCmdShow = -1; // set to default after first time
}
ActivateFrame(nCmdShow); //在这里第一次显示窗口
//...
}
->CFrameWnd::ActivateFrame(int nCmdShow)
// nCmdShow is the normal show mode this frame should be in
{
// translate default nCmdShow (-1)
if (nCmdShow == -1)
{
   if (!IsWindowVisible())
    nCmdShow = SW_SHOWNORMAL;
   else if (IsIconic())
    nCmdShow = SW_RESTORE;
}

// bring to top before showing
BringToTop(nCmdShow);

if (nCmdShow != -1)
{
  // show the window as specified
   ShowWindow(nCmdShow); //第一次显示窗口

  // and finally, bring to top after showing
   BringToTop(nCmdShow);
}
}
->***

从上面可以看出,CWinApp::ProcessShellCommand函数创建了窗口并显示,这是窗口第一次显示,先于:
m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
m_pMainWnd->UpdateWindow();


怎么解决问题? 让窗口第一次显示就最大化?

CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line
//在ParseCommandLine之后,ProcessShellCommand之前,添加这句!!!
m_nCmdShow = SW_SHOWMAXIMIZED;
if (!ProcessShellCommand(cmdInfo))
   return FALSE;

// The one and only window has been initialized, so show and update it.
m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
m_pMainWnd->UpdateWindow();

posted @ 2009-07-14 07:42 卡洛shll 阅读(834) | 评论 (0)编辑 收藏

仅列出标题
共3页: 1 2 3