了解 C/C++ 程序的清单生成

清单是唯一标识程序集的 XML 文档。 它包含用于绑定和激活的信息,例如 COM 类、接口和类型库。 清单既可以是外部 XML 文件,也可以是嵌入到应用程序或程序集内的资源。 独立应用程序的清单用于管理共享的并行程序集的名称和版本,应用程序应当在运行时绑定到这些程序集。 并行程序集的清单用于指定其对名称、版本、资源和其他程序集的依赖项。

可以通过两种方式为独立应用程序或并行程序集创建清单。 首先,程序集的作者可以按照规则和命名要求手动创建清单文件。 有关详细信息,请参阅清单文件参考。 或者,如果程序仅依赖于 MSVC 程序集(如 CRT、MFC、ATL 或其他程序集),则链接器可以自动生成清单。

MSVC 库的标头包含程序集信息,当应用程序代码中包含库时,链接器将使用此程序集信息来形成最终二进制文件的清单。 默认情况下,链接器不会在二进制文件中嵌入清单文件。 将清单作为外部文件可能并不适用于所有情况。 例如,建议私有程序集具有嵌入清单。 在命令行生成(如使用 NMAKE 生成代码)中,可以使用 /MANIFEST:EMBED 链接器选项嵌入清单。 或者,可以使用清单工具嵌入清单。 有关详细信息,请参阅命令行上的清单生成。 在 Visual Studio 中生成时,可以通过在“项目属性”对话框中设置清单工具的属性来嵌入清单,如下一部分所述。

Visual Studio 中的清单生成

你可以告诉 Visual Studio 在项目的“属性页”对话框中为特定项目生成清单文件。 在“配置属性”下,选择“链接器”>“清单文件”>“生成清单”。 默认情况下,新项目的项目属性设置为生成清单文件。 不过,可使用项目的“生成清单”属性禁用项目的清单生成。 如果此属性设置为“是”,则将生成此项目的清单。 否则,链接器在解决应用程序代码的依赖项时会忽略程序集信息,并且不生成清单。

Visual Studio 中的生成系统允许将清单嵌入到最终的二进制应用程序文件中,或作为外部文件生成。 此行为由“项目属性”对话框中的“嵌入清单”选项控制。 要设置此属性,请打开“清单工具”节点,然后选择“输入和输出”。 如果不嵌入清单,则它将被生成为外部文件,并保存在与最终二进制文件相同的目录中。 如果清单嵌入,Visual Studio 将使用以下过程嵌入最终清单:

  1. 将源代码编译为对象文件后,链接器将收集依赖程序集信息。 在链接最终二进制文件时,链接器会生成一个中间清单,稍后该清单将用于生成最终清单。

  2. 生成中间清单并完成链接后,清单工具将合并最终清单,并将它另存为外部文件。

  3. 然后,项目生成系统将进行检测,确定在由清单工具生成的清单中,其信息是否有别于已嵌入二进制文件中的清单所包含的信息。

  4. 如果二进制文件中嵌入的清单不同于清单工具生成的清单,或二进制文件中不包含嵌入的清单,则 Visual Studio 将再次调用链接器,将外部清单文件作为资源嵌入到二进制文件中。

  5. 如果二进制文件中嵌入的清单与清单工具生成的清单相同,则生成系统将继续下面的生成步骤。

清单会作为文本资源嵌入到最终二进制文件中。 可通过在 Visual Studio 中将最终二进制文件作为文件打开来查看该清单。 若要确保清单指向正确的库,请按照了解 Visual C++ 应用程序的依赖关系中的所述步骤。 或者,按照故障排除一文中所述的建议进行操作。

命令行上的清单生成

使用 NMAKE 或类似工具从命令行生成 C/C++ 应用程序时,会在链接器处理所有对象文件并生成最终二进制文件后生成清单。 链接器收集存储在对象文件中的程序集信息,并将此信息合并到最终清单文件中。 默认情况下,链接器将生成一个名为 <binary_name>.<extension>.manifest 的文件,用于描述最终二进制文件。 链接器可以通过指定 /MANIFEST:EMBED 链接器选项将清单文件嵌入到二进制文件中。

有几种其他方法可以将清单嵌入到最终二进制文件中,如使用清单工具 (mt.exe) 或将清单编译到资源文件中。 嵌入清单时,必须遵循特定规则才能启用增量链接、签名和编辑并继续等功能。 下一部分讨论了这些规则以及其他选项。

如何将清单嵌入到 C/C++ 应用程序中

建议将应用程序或库的清单嵌入到最终二进制文件中。 此方法可在大多数情况下确保正确的运行时行为。 默认情况下,Visual Studio 在生成项目时将尝试嵌入清单。 但是,如果使用 NMAKE 生成应用程序,则必须对生成文件进行一些更改。 本节演示如何更改生成文件,使它自动将清单嵌入到最终二进制文件中。

两种方法

可以通过两种方法在应用程序或库中嵌入清单。

  1. 如果不执行增量生成,则可以通过将类似于以下内容的命令行用作后期生成步骤来直接嵌入清单:

    mt.exe -manifest MyApp.exe.manifest -outputresource:MyApp.exe;1
    

    mt.exe -manifest MyLibrary.dll.manifest -outputresource:MyLibrary.dll;2
    

    对于 EXE 使用 1,对 DLL 使用 2。

  2. 如果要执行增量生成,请使用以下步骤:

    • 链接二进制文件以生成 MyApp.exe.manifest 文件。

    • 将清单转换为资源文件。

    • 重新链接(以增量方式),以将清单资源嵌入到二进制文件中。

以下示例演示如何更改生成文件以结合使用这两种方法。

生成文件(之前)

考虑从一个文件生成的简单应用程序 MyApp.exe 的 NMAKE 脚本:

# build MyApp.exe
!if "$(DEBUG)" == "1"
CPPFLAGS=$(CPPFLAGS) /MDd
LFLAGS=$(LFLAGS) /INCREMENTAL
!else
CPPFLAGS=$(CPPFLAGS) /MD
!endif

MyApp.exe : MyApp.obj
    link $** /out:$@ $(LFLAGS)

MyApp.obj : MyApp.cpp

clean :
    del MyApp.obj MyApp.exe

如果在未更改的情况下使用 Visual Studio 运行此脚本,则会成功创建 MyApp.exe。 它还会创建外部清单文件 MyApp.exe.manifest,供操作系统用于在运行时加载依赖程序集。

MyLibrary.dll 的 NMAKE 脚本非常相似于以下内容:

# build MyLibrary.dll
!if "$(DEBUG)" == "1"
CPPFLAGS=$(CPPFLAGS) /MDd
LFLAGS=$(LFLAGS) /DLL /INCREMENTAL

!else
CPPFLAGS=$(CPPFLAGS) /MD
LFLAGS=$(LFLAGS) /DLL

!endif

MyLibrary.dll : MyLibrary.obj
    link $** /out:$@ $(LFLAGS)

MyLibrary.obj : MyLibrary.cpp

clean :
    del MyLibrary.obj MyLibrary.dll

生成文件(之后)

若要使用嵌入清单进行生成,必须对原始生成文件进行四个小更改。 对于 MyApp.exe 生成文件:

# build MyApp.exe
!include makefile.inc
#^^^^^^^^^^^^^^^^^^^^ Change #1. (Add full path if necessary.)

!if "$(DEBUG)" == "1"
CPPFLAGS=$(CPPFLAGS) /MDd
LFLAGS=$(LFLAGS) /INCREMENTAL
!else
CPPFLAGS=$(CPPFLAGS) /MD
!endif

MyApp.exe : MyApp.obj
    link $** /out:$@ $(LFLAGS)
    $(_VC_MANIFEST_EMBED_EXE)
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Change #2

MyApp.obj : MyApp.cpp

clean :
    del MyApp.obj MyApp.exe
    $(_VC_MANIFEST_CLEAN)
#^^^^^^^^^^^^^^^^^^^^^^^^ Change #3

!include makefile.target.inc
#^^^^^^^^^^^^^^^^^^^^^^^^^ Change #4. (Add full path if necessary.)

对于 MyLibrary.dll 生成文件:

# build MyLibrary.dll
!include makefile.inc
#^^^^^^^^^^^^^^^^^^^^ Change #1. (Add full path if necessary.)

!if "$(DEBUG)" == "1"
CPPFLAGS=$(CPPFLAGS) /MDd
LFLAGS=$(LFLAGS) /DLL /INCREMENTAL

!else
CPPFLAGS=$(CPPFLAGS) /MD
LFLAGS=$(LFLAGS) /DLL

!endif

MyLibrary.dll : MyLibrary.obj
    link $** /out:$@ $(LFLAGS)
    $(_VC_MANIFEST_EMBED_DLL)
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Change #2.

MyLibrary.obj : MyLibrary.cpp

clean :
    del MyLibrary.obj MyLibrary.dll
    $(_VC_MANIFEST_CLEAN)
#^^^^^^^^^^^^^^^^^^^^^^^^ Change #3.

!include makefile.target.inc
#^^^^^^^^^^^^^^^^^^^^^^^^^ Change #4. (Add full path if necessary.)

生成文件现在包含两个执行实际工作的文件,即 makefile.incmakefile.target.inc

创建 makefile.inc,并将以下内容复制到其中:

# makefile.inc -- Include this file into existing makefile at the very top.

# _VC_MANIFEST_INC specifies whether build is incremental (1 - incremental).
# _VC_MANIFEST_BASENAME specifies name of a temporary resource file.

!if "$(DEBUG)" == "1"
CPPFLAGS=$(CPPFLAGS) /MDd
LFLAGS=$(LFLAGS) /INCREMENTAL
_VC_MANIFEST_INC=1
_VC_MANIFEST_BASENAME=__VC90.Debug

!else
CPPFLAGS=$(CPPFLAGS) /MD
_VC_MANIFEST_INC=0
_VC_MANIFEST_BASENAME=__VC90

!endif

####################################################
# Specifying name of temporary resource file used only in incremental builds:

!if "$(_VC_MANIFEST_INC)" == "1"
_VC_MANIFEST_AUTO_RES=$(_VC_MANIFEST_BASENAME).auto.res
!else
_VC_MANIFEST_AUTO_RES=
!endif

####################################################
# _VC_MANIFEST_EMBED_EXE - command to embed manifest in EXE:

!if "$(_VC_MANIFEST_INC)" == "1"

#MT_SPECIAL_RETURN=1090650113
#MT_SPECIAL_SWITCH=-notify_resource_update
MT_SPECIAL_RETURN=0
MT_SPECIAL_SWITCH=
_VC_MANIFEST_EMBED_EXE= \
if exist $@.manifest mt.exe -manifest $@.manifest -out:$(_VC_MANIFEST_BASENAME).auto.manifest $(MT_SPECIAL_SWITCH) & \
if "%ERRORLEVEL%" == "$(MT_SPECIAL_RETURN)" \
rc /r $(_VC_MANIFEST_BASENAME).auto.rc & \
link $** /out:$@ $(LFLAGS)

!else

_VC_MANIFEST_EMBED_EXE= \
if exist $@.manifest mt.exe -manifest $@.manifest -outputresource:$@;1

!endif

####################################################
# _VC_MANIFEST_CLEAN - command to clean resources files generated temporarily:

!if "$(_VC_MANIFEST_INC)" == "1"

_VC_MANIFEST_CLEAN=-del $(_VC_MANIFEST_BASENAME).auto.res \
    $(_VC_MANIFEST_BASENAME).auto.rc \
    $(_VC_MANIFEST_BASENAME).auto.manifest

!else

_VC_MANIFEST_CLEAN=

!endif

# End of makefile.inc
####################################################

现在创建 makefile.target.inc,并将以下内容复制到其中:

# makefile.target.inc - include this at the very bottom of the existing makefile

####################################################
# Commands to generate initial empty manifest file and the RC file
# that references it, and for generating the .res file:

$(_VC_MANIFEST_BASENAME).auto.res : $(_VC_MANIFEST_BASENAME).auto.rc

$(_VC_MANIFEST_BASENAME).auto.rc : $(_VC_MANIFEST_BASENAME).auto.manifest
    type <<$@
#include <winuser.h>
1RT_MANIFEST"$(_VC_MANIFEST_BASENAME).auto.manifest"
<< KEEP

$(_VC_MANIFEST_BASENAME).auto.manifest :
    type <<$@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
</assembly>
<< KEEP

# end of makefile.target.inc

另请参阅

生成 C/C++ 独立应用程序和并行程序集
独立应用程序和并行程序集的概念
C/C++ 独立应用程序和并行程序集疑难解答
/INCREMENTAL(增量链接)
/MANIFEST(创建并行程序集清单)
强名称程序集(程序集签名)(C++/CLI)
编辑并继续