NMake的原理-makefile入门

来源:互联网 发布:淘宝商家怎么找到淘客 编辑:程序博客网 时间:2024/06/04 05:58

makefile入门(转)

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既不是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本身的功能和作用而言,我们才刚刚开始。




原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 文件大于100发不了微信怎么办 微信的传送文件大于100怎么办 微信钱包话费充值错误怎么办 微信转账到不了账也退不回是怎么办 求人办事微信发红包对方不收怎么办 微信上交了订金对方不退怎么办 交通事故对方伤员堵大门搂腿怎么办 电脑开机桌面文件都没了怎么办 qq飞车手游队长换了微信群怎么办 qq飞车手游登录授权失败怎么办 安装时提示安装包发现错误怎么办 苹果6p升级系统验证失败怎么办 w10开不了机无限重启怎么办 微信朋友圈里的表情图打不开怎么办 金立手机微信启动录音被拒绝怎么办 微信帐号解封后漂流瓶不能用怎么办 微信怎么在电脑上登不上去怎么办 玩旧版60级魔兽经常花屏怎么办? 我的世界手机版物品栏不见了怎么办 苹果手机掉进水里出现花屏该怎么办 球球大作战还没进去停止运行怎么办 ps3 e3硬破芯片坏了怎么办 电话打开后页面上没有东西怎么办 WPS在电脑安装后卸载不了怎么办 ps总要以管理员的身份打开怎么办 3d关的慢保存慢怎么办 无法与服务器建立可靠的连接怎么办 被抵押的房子开发商不解押怎么办 手机系统语言是英文没有中文怎么办 w7主机网插口灯不亮了没网怎么办 电脑用了5年变得很卡了怎么办 苹果6s系统占12g怎么办 百度网盘下载的压缩包打不开怎么办 三星手机微信安装包解析错误怎么办 下身流出来的东西有异味很重怎么办 鞋脚底总有一股酸臭的味道怎么办 腋窝总是有股酸臭的味道应该怎么办 房间里总是有灰尘的味道怎么办 香水喷多了衣服味道太呛怎么办 香水喷在衣服上变黄了怎么办 84喷在白衣服上变红怎么办