windows 下重定向dll, SxS and .manifest

@vrqq  August 21, 2021
先看一篇文章简易入门: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

其他经验

使用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 把我们之前的覆盖了。。


添加新评论