关于微软的Manifest的那些事

什么是Manifest?

在Windows中,manifest(清单)是一个XML文件,根据清单类型不同,可以分为四种:

Manifest描述
程序集清单描述名称、版本、资源、依赖的side-by-side程序集
应用程序清单描述共享的side-by-side的程序集版本和名称,可能包含私有side-by-side的程序集元数据
应用程序配置文件重定向每一个应用程序依赖的程序集版本
发布者配置文件使用发布者配置重定向一整块的程序集版本

程序集清单

要想理解程序集清单,首先要了解什么是程序集?

百度百科上说的程序集的概念是:

​ 经由编译器编译得到的,供CLR进一步编译执行的那个中间产物,在WINDOWS系统中,它一般表现为·dll或者是·exe的格式,但是要注意,它们跟普通意义上的WIN32可执行程序是完全不同的东西,程序集必须依靠CLR才能顺利执行。

但CLR是.net平台才有的,那么C++编译出来的程序是程序集吗?

根据微软官方的解释:

An assembly is a fundamental unit for naming, binding, versioning, deploying, or configuring a block of programming code.

程序集是用于命名、绑定、版本控制、部署或配置程序代码块的基本单元

Applications with common functionality may run shared blocks of programming code which are referred to as modules or code assemblies.

应用程序具有通用部分的功能并且可以共享运行的程序代码块被称为模块或代码集

These code assemblies may be placed in DLLs or COM assemblies.

这些代码集通常集成在dll或com集中

The infrastructure for the safe sharing of assemblies is referred to as side-by-side assembly sharing.

用于安全共享程序集的基础结构成为并行(side-by-side)程序集共享

从上面的话,我们可以把程序集理解成组件,主要以dll、com形式表现出来。

最后一句话,则提出来并行(side-by-side)程序集的概念。

Side-by-side assemblies are code assemblies described by manifests and authored so that multiple versions may run at the same time without conflicting with each other.

并行程序集是带有清单描述的程序集,以便多个版本同时运行而不会互相冲突

我们现在电脑通常安装了几个Visual C++运行库,有2008、2010、2005,它们就是并行程序集,当电脑的程序运行时需要2008的组件就会调用2008的。

程序集又可以分为共享和私有两种,我们画个图来理解这个概念

我们可以看一下程序集清单的组成结构:

元素属性是否必须说明
assemblyyesxml的根节点
manifestVersion固定为:1.0
noInheritableNo多线程不继承清单,如果没有则默认继承。会被应用清单覆盖
assemblyIdentityYes程序集唯一标识
typeYes该值必须是win32
nameYes程序集的唯一名称
languageNo标识程序集的语言
processorArchitectureNo处理器架构,x86或ia64
versionYes指定程序集版本,格式如:mmmmm.nnnnn.ooooo.ppppp,数值范围0-65535(含)
publicKeyTokenNo16个字符的十六进制字符串,表示对程序集签名的公共密钥sha-1哈希的最后8个字节,对于共享的并行程序集是必须的
dependencyNo包含至少一个dependentAssembly子元素
dependentAssemblyNo第一个子元素必须为assemblyIdentity,描述该程序集依赖哪些程序集
fileNo该程序集使用的文件
nameYes文件名称,例如,Conctl32.dll
hashalgNo文件的hash算法,该值为SHA1
hashNo文件的hash值
comClassNoCOM组件
descriptionNoclass名称
clsidYes唯一标识类的GUID
threadingModelNo进程内COM类使用的线程模型
tlbidNo此COM组件的GUID
progidNo此COM组件的ProgID
miscStatusNo和COM组件有关,具体可以参考OLEMISC枚举
miscStatusIconNo和COM组件有关,具体可以参考OLEMISC枚举
miscStatusContentNo和COM组件有关,具体可以参考OLEMISC枚举
miscStatusDocPrintNo和COM组件有关,具体可以参考OLEMISC枚举
miscStatusThumbnailNo和COM组件有关,具体可以参考OLEMISC枚举
typelibNo类型库
tlbidYes类型库的唯一ID
versionYes版本
helpdirYes帮助目录
resourceidNo资源ID
flagsNo标志
comInterfaceExternalProxyStubNocom接口扩展代理存根
iidYes代理接口的IID
baseInterfaceNo基类的IID
numMethodsNo接口实现的方法数
nameNo接口名称
tlbidNo类型库
proxyStubClsid32No将IID映射到32位代理DLL中的CLISID
comInterfaceProxyStubNocom接口代理存根
iidYes代理接口的IID
nameYes接口名称
tlbidNo类型库
baseInterfaceNo基类接口IID
numMethodsNo接口实现的方法数
proxyStubClsid32No代理存根32位CLISID
threadingModelNo进程内COM使用的线程模型
windowClassNo版本化的Windows类
versionedNo版本,默认yes

清单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity type="win32" name="Microsoft.Tools.SampleAssembly" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="0000000000000000"/>
<file name="sampleu.dll" hash="3eab067f82504bf271ed38112a4ccdf46094eb5a" hashalg="SHA1">
<comClass description="Font Property Page" clsid="{0BE35200-8F91-11CE-9DE3-00AA004BB851}"/>
<comClass description="Color Property Page" clsid="{0BE35201-8F91-11CE-9DE3-00AA004BB851}"/>
<comClass description="Picture Property Page" clsid="{0BE35202-8F91-11CE-9DE3-00AA004BB851}"/>
</file>
<file name="bar.dll" hash="ac72753e5bb20446d88a48c8f0aaae769a962338" hashalg="SHA1"/>
<file name="foo.dll" hash="a7312a1f6cfb46433001e0540458de60adcd5ec5" hashalg="SHA1">
<comClass description="Registrar Class" clsid="{44EC053A-400F-11D0-9DCD-00A0C90391D3}" progid="ATL.Registrar"/>
<comInterfaceProxyStub iid="{B6EA2051-048A-11D1-82B9-00C04FB9942E}" name=" IAxWinAmbientDispatch " tlbid="{34EC053A-400F-11D0-9DCD-00A0C90391D3}"/>
<typelib tlbid="{44EC0535-400F-11D0-9DCD-00A0C90391D3}" version="1.0" helpdir=""/>
</file>
<file name="sampledll.dll" hash="ba62960ceb15073d2598379307aad84f3a73dfcb" hashalg="SHA1"/>
<windowClass>ToolbarWindow32</windowClass>
<windowClass>ComboBoxEx32</windowClass>
<windowClass>sample_trackbar32</windowClass>
<windowClass>sample_updown32</windowClass>
</assembly>

共享程序集

我们先从共享讨论,分别从共享的并行程序集和共享的非并行程序集

在xp以前都是使用的是非并行的程序集,在同一台电脑上是这样的:

后来使用并行程序集,在同一台电脑上就可以这样:

这种方式解决了不同应用程序使用的共享dll版本不一样的问题

共享的并行程序集

共享的并行程序集安装在C:\Windows\winsxs中,程序集清单位置是C:\Windows\winsxs\Manifests。

我们随便找了一个文件,文件名为:

x86_microsoft.windows.gdiplus_6595b64144ccf1df_1.0.7601.24542_none_6cb5a8e3070802c6.manifest

内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:cmiv2="urn:schemas-microsoft-com:asm.v3" cmiv2:copyright="Copyright (c) Microsoft Corporation. All Rights Reserved.">
<assemblyIdentity name="Microsoft.Windows.GdiPlus" version="1.0.7601.24542" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" type="win32" />
<file hash="95281000a14d442e38fd0fa84afe5a756ac09587" hashalg="SHA1" name="GdiPlus.dll" cmiv2:sourceName="" cmiv2:importPath="$(build.nttree)\asms\10\msft\windows\gdiplus\">
<signatureInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<signatureDescriptor pageHash="true" />
</signatureInfo>
<asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2">
<dsig:Transforms xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
</dsig:Transforms>
<dsig:DigestMethod xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
<dsig:DigestValue xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">iJX0DKKm9MgXZ+ips3/DbVbY4aRvyIIsuaxziGzX8VI=</dsig:DigestValue>
</asmv2:hash>
</file>
<memberships xmlns="urn:schemas-microsoft-com:asm.v3">
<categoryMembership>
<id name="Microsoft.Windows.Software.System.Multimedia_and_Graphics.Infrastructure" version="6.1.7601.24542" processorArchitecture="x86" language="neutral" buildType="release" publicKeyToken="1122334455667788" />
</categoryMembership>
<categoryMembership>
<id name="Microsoft.Windows.Fusion_Components_All" version="6.1.7601.24542" processorArchitecture="x86" language="neutral" buildType="release" publicKeyToken="1122334455667788" />
</categoryMembership>
<categoryMembership>
<id name="Microsoft.Windows.Categories" version="1.0.0.0" publicKeyToken="365143bb27e7ac8b" typeName="BootRecovery" />
</categoryMembership>
</memberships>
</assembly>
共享的非并行程序集

不讨论

私有程序集

私有程序集可以分为带清单的程序集(并行程序集)和不带清单的程序集(非并行程序集)。

私有的并行程序集

带清单的私有程序集很少见,且绝大多数都只是规定了运行等级。

我在AcroRd32.dll里面找到了一个,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<!-- 程序集描述-->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="Adobe.Reader" version="10.0.0.0" processorArchitecture="x86" publicKeyToken="0000000000000000"/>
<!--依赖的程序集,总共依赖两个,一个是Adobe.Reader.Dependencies(私有),
另一个是Microsoft.Windows.Common-Controls(共享)-->
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Adobe.Reader.Dependencies" version="10.0.0.0" processorArchitecture="x86" publicKeyToken="0000000000000000"/>
</dependentAssembly>
</dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
私有的非并行程序集

暂不讨论

程序集的搜索顺序

具体可以参照微软文档

程序集清单的嵌入和非嵌入

程序集清单嵌入就没有什么好讲的,用ResourceHacker就可以查看嵌入的清单。

微软文档上建议都使用嵌入式清单,但是有一种情况,你使用的是第三方的com组件,它并没有清单文件,你可以使用非内嵌的方式。

文档上提:如果采取非嵌入式清单,则程序集的名称与清单的名称不能和dll名称一样,非嵌入式清单的搜索顺序参考程序集搜索顺序

应用清单

应用清单就会非常少了,下面界面组成应用清单的xml结构:

元素属性必须说明
assemblyYesxml的根节点
manifestVersionYes固定为:1.0
noInheritNo多线程不继承清单,如果没有则默认继承。一般默认继承,和激活上下文有关。
assemblyIdentityYes唯一标识应用程序
typeYes指定应用程序或程序集类型,该值必须为win32
nameYes唯一命名
languageNo标识应用程序或程序集的语言
processorArchitectureNo处理器架构,x86、amd64、arm、arm64
versionYes指定应用程序或程序集的版本,使用四部分版本格式。
publicKeyTokenNo对应用程序集签名的公钥密钥的SHA-1的最后8个字节,所有共享的并行组件均必须
compatibilityNo兼容性节点
applicationNo应用节点
supportedOSIdNo应用下面的支持操作系统,详细请参考supportedOS
maxversiontestedIdNo支持windows的最高版本
dependencyNo依赖节点
dependentAssemblyNo依赖的程序集节点,子节点为assemblyIdentity
fileNo指定应用程序专用的文件,如Comctl32.dll。应用程序会在启动的时候就加载进来
nameNo文件名
hashalgNo文件的hash算法
hashNo文件的hash值
activeCodePageNo强制进程使用UTF-8作为进程代码页
autoElevateNo指定是否启用自动提升权限,TRUE代表已启用
disableThemingNo是否禁用UI使用主题
disableWindowFilteringNo是否禁用窗口过滤
dpiAwareNo是否支持dpi,有几个选项。详看dpiAware
dpiAwarenessNo详看dpiAware
gdiScalingNo是否启用GDI缩放,最低位Windows 10版本1703,Windows7就别想了
highResolutionScrollingAwareNo是否启用高分辨率滚动感知
longPathAwareNo启用长度超过MAX_PATH的长路径,Windows 10 1607版本以上才支持
printerDriverIsolationNo是否启用打印机驱动程序隔离
ultraHighResolutionScrollingAwareNo是否启用超高分辨率滚动感知
msixNo看文档
heapTypeNo覆盖Win32堆的默认实现,通常可减少内存使用,Windows10 2004版以上支持(鸡肋)

结尾

理论终于讲完了,至于应用程序配置文件和发布者配置文件,我目前用不上,不看了,看的头大。接下来另起一篇,讲讲怎么利用清单调用免注册COM组件的。下一篇链接地址:使用清单Manifest免注册调用COM组件

参考文献

  1. Microsoft Doc:Manifest Files Reference
  2. 百度百科:程序集
  3. Microsoft Doc:关于独立应用程序和并行程序集
  4. Microsoft Doc:免注册COM互操作
  5. excelhome论坛:介绍两种免注册使用COM组件的方法

评论