先看一篇文章简易入门:https://www.jianshu.com/p/64330b250f30
再看官方文档:https://docs.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference
前言 什么是manifest
其实很简单,就是Linux下RUNPATH的加强版,只是文档写的云里雾里而已!
- resource: 每个dll/exe内部可以放一些七七八八的文件,比如一个文本文档,一个mp3,一个图标,就像压缩包一样。。
- 用vs像打开文件一样 打开dll/exe就可以看到其内含的rc
以偏概全的通俗解释:
manifest列出了当前程序的dll依赖,以及dll之间的依赖,manifest通常位于dll内部作为rc,每个dll都应含有一个manifest。
manifest是xml格式的文档,文档内还包含当前dll的版本、签名等等。
官方把manifest分为了application manifest和assembly manifest,按照stackoverflow中的解释,前者是表述操作系统交互的,后者是表述程序内部依赖的。但我理解前者是整个进程(thread)的全局表,后者是当前context的局部表,只是在用的地方不同,内容差不多,下文样例中 不区分这两个概念。(其实有区别,看m$官网定义 内部xml字段不同)
https://stackoverflow.com/questions/3476089/difference-between-application-manifest-and-assembly-manifest
Activation Contexts
官方文档: https://docs.microsoft.com/en-us/windows/win32/sbscs/activation-contexts
官方文档写的云里雾里 建议看完blog回头去看
当某个manifest载入时,会创建新的context,并使inactive previous context (不继承),以此来表明当前dll的运行所需环境。这样当前程序就可以同时(间接)依赖 不同版本的第三方库了。
举例,下文是个目录结构,每个文件内部均含有一个manifest作为资源文件:
root
|- main.exe {manifest: dep=a} //文件头: NEEDED a.dll
|- a.dll {manifest: dep=mod1, ssl} //文件头: NEEDED ssl.dll, mod1.dll
|- ssl.dll {manifest: no-dep}
|- mod1 (directory)
|- mod1.dll { manifest: name=mod1, dep=ssl} //文件头: NEEDED ssl.dll
|- ssl.dll { manifest: no-dep}
编译期间:
main.exe <= link{main.o, a.dll}
mod.dll <= link{mod.o, modcalc.dll}
现在开始双击main.exe
- 首先会载入main.exe.manifest (在main.exe内部作为资源文件), 载入main.manifest并激活当前context{main.context}
- 当前context提供了 root/a.dll, root/ssl.dll,然后当前文件头需要load a.dll 好的此时载入之
准备加载a.dll进内存了,先把a.dll.manifest拉出来看看,激活当前a.context,之前的main.context先屏蔽掉
- 发现manifest提供了root/mod1.dll, root/ssl.dll,正好满足了a.dll文件头中的需要(由linker制作的文件头),好的a.dll可以了
准备加载mod1.dll,active mod1.context,之前的context不会继承到这里,所以当前可用dll为: root/mod1/mod1.dll和 root/mod1/ssl.dll
- 由于context不会向下继承,所以此时root/mod1/mod1.dll只能看得见root/mod1/ssl.dll,而上一层的ssl.dll不在他可用范围内,就这样保证的不同版本的dll可以同时使用!
注意,如果某个dll内部不含manifest,他就不会创建context,就只好沿用上一个context了!
例如上面例子中:若root/mod1/mod1.dll内没有manifest,就会在载入mod1.dll时使用的还是a.context,导致mod1.dll实际使用的是root/ssl.dll,而不是他想要的root/mod1/ssl.dll,然后就挂了!
这么看下来和linux下的RUNPATH异曲同工吧!
全局和局部
第一篇参考文章中 https://www.jianshu.com/p/64330b250f30 他给入口exe外挂了.manifest,我理解这里直接变全局(application manifest)
后记
想把某个第三方发布的一大坨dll放进一个文件夹?
假如放进root/kmap/那么无中生有一个root/kmap/kmap.manifest,在这里写就行了,依据是manifest search order
https://docs.microsoft.com/en-us/windows/win32/sbscs/assembly-searching-sequence
其他经验
- 对于exe入口,manifest完全可以外挂,即使编译时什么参数也不加,后期新建完也正常起效。例如a.exe 和 a.exe.manifest
- 对于dll如果外挂,例如b.dll和b.dll.manifest,当某exe启动时load b.dll时,因为按照搜索顺序 b.dll在先,故manifest就不会被加载,此时就需要mt.exe将manifest合并进dll(见下文)
- 微软规定的版本号规则 https://docs.microsoft.com/en-us/dotnet/standard/assembly/versioning
- manifest的第二个用法是,可以以版本号查找 https://docs.microsoft.com/en-us/dotnet/standard/assembly/strong-named
使用chrome gn时为了一同生成.manifest添加的内容
ldflags = [
"/manifestdependency:type='win32' name='ctpse_6.3.15_amd64' version='6.3.15.0' processorArchitecture='ia64'",
"/manifest:embed",
]
ldflags = ["/manifestdependency:type='win32' name='macli_3.4_win32' version='3.4.0.0' processorArchitecture='x86'"]
运行exe时提示sxs写的不对
新开一个窗口运行如下
sxstrace.exe /?
sxstrace.exe Trace -logfile:mysxs.etl
sxstrace Parse -logfile:mysxs.etl -outfile:mysxs.txt
不想额外再有个.manifest文件,想合并进exe/dll
话说回来 dll和exe就差个main 有啥不一样
https://docs.microsoft.com/en-us/cpp/build/how-to-embed-a-manifest-inside-a-c-cpp-application?view=msvc-160
mt.exe -manifest MyApp.exe.manifest -outputresource:MyApp.exe;1
mt.exe -manifest MyLibrary.dll.manifest -outputresource:MyLibrary.dll;2
manifest参数表
https://docs.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference
按照文中说的配置了,还是运行就提示找不到xxx.dll
先打开sxstrace,看最后停留在了哪个context,找到提供这个context的文件是哪个,打开这个dll瞧瞧
常出现在打包第三方库时,发现只有直接link的那个可以装载,间接的load dll都找不到,这里就看是不是有哪个dll带了一个空的manifest,在这个点上 创建了新的context 把我们之前的覆盖了。。