1. 寫在前面的話
我們在開發(fā)應(yīng)用程序時陡鹃,一般會引入一些第三方庫冀痕,通常情況下台谍,我們是把這些第三方依賴文件放到應(yīng)用程序所處目錄沛励,這樣應(yīng)用程序啟動時就能正確找到相關(guān)依賴文件甩挫。但當依賴文件比較多贴硫,我們希望對依賴的文件進行歸類,放置到不同的目錄下以便管理伊者,這個時候應(yīng)用程序的manifest就派上用場了英遭。
2. 并行程序集(Side-by-Side Assembly)
在介紹應(yīng)用程序的manifest之前,需要了解一下并行程序集(Side-by-Side Assembly)亦渗。什么是并行程序集呢? 并行程序集是微軟為了解決DLL Hell問題而提出的一種解決方案挖诸,它采用manifest文件掃描組件之間的依賴關(guān)系。其工作原理如下圖所求:
簡單說明一下法精,微軟在未提出Side-by-Side Assembly之前多律,應(yīng)用程序啟動時按照一定的規(guī)則加載DLL痴突。通常情況下,應(yīng)用程序會采用動態(tài)鏈接方式共享一些操作系統(tǒng)提供的基礎(chǔ)庫文件狼荞,當Windows更新共享庫且共享庫不能向后兼容時(DLL自身并不能向后兼容辽装,這種情況通常發(fā)生在DLL的內(nèi)存布局發(fā)生了改變),那些依賴于老版本共享庫的應(yīng)用程序就不能正常工作了。為了解決這個問題相味,微軟重寫了DLL動態(tài)加載子系統(tǒng)拾积,提出了并行程序集的解決方案,即允許多個版本的庫共同存在攻走,應(yīng)用程序通過manifest描述自身所依賴的文件殷勘,SxS Manager再通過manifest按照一定的規(guī)則找到應(yīng)用程序的依賴文件,使應(yīng)用程序正確工作昔搂。
3. 程序集查找順序
和DLL加載順序類似玲销,SxS Manager在查找應(yīng)用程序的依賴程序集時也按照一定的規(guī)則進行查找。一般情況下摘符,其查找規(guī)則如下贤斜,如果應(yīng)用程序需要多語言支持,請參考這里逛裤。
- Side-by-side searches the WinSxS folder.
- \\<appdir>\<assemblyname>.DLL
- \\<appdir>\<assemblyname>.manifest
- \\<appdir>\<assemblyname>\<assemblyname>.DLL
- \\<appdir>\<assemblyname>\<assemblyname>.manifest
SxS Manager首先查找共享程序集, 共享程序集通常在Windows下的WinSxS目錄下瘩绒。如果未找到則會在應(yīng)用程序所處目錄按照上面順序查找相應(yīng)的程序集名。
4. 應(yīng)用程序manifest
上面已經(jīng)講并行程序集的工作原理和并行程序集的查找順序带族,接下來說一下如何用manifest描述應(yīng)用程序的依賴程序集以及如何將manifest嵌入到應(yīng)用程序中锁荔。
關(guān)于應(yīng)用程序的manifest詳細介紹請參考這里, 下面以應(yīng)用程序SampleApp為例,其manifest為SampleApp.exe.manifest, 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="SampleApp" version="1.0.0.0" processorArchitecture="x86"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="SampleAssembly" version="1.0.0.0" processorArchitecture="x86"/>
</dependentAssembly>
</dependency>
</assembly>
這里重點關(guān)注dependency結(jié)點蝙砌,該結(jié)點描述了應(yīng)用程序SampleApp依賴于程序集SampleAssembly阳堕。更多關(guān)于Assembly Manifest的XML描述請參考這里。
有了上面的manifest择克,應(yīng)用程序啟動時恬总,為了讓SxS Manager能夠正確查找到依賴文件,還需要將SampleApp.exe.manifest嵌入到應(yīng)用程序中肚邢,有兩種方式可以嵌入:
方法一:
如果采用Visual Studio構(gòu)建應(yīng)用程序壹堰,默認情況下,VS會將manifest嵌入到應(yīng)用程序中骡湖。按下面方式設(shè)置應(yīng)用程序的依賴程序集:
Properties > Configuration Properties > Linker > Manifest File > Additional Manifest Dependencies >
type='win32' name='SampleAssembly' version='1.0.0.0' processorArchitecture='x86'
也可以采用comment prama
方式:
#pragma comment(linker, "\"/manifestdependency:type='win32' name='SampleAssembly' version='1.0.0.0' processorArchitecture='x86' \"")
設(shè)置好之后贱纠,重新編譯、鏈接响蕴,生成SampleApp.exe并巍。如果想知道上面依賴信息是否已經(jīng)成功嵌入到SampleApp.exe中,也有兩種方式查看:
1. 如果使用Visual Studio, 可以直接在Visual Studio中打開應(yīng)用程序换途,如下:
2. 也可以使用SDK提供的工具mt.exe, 命令如下:
mt.exe -inputresource:SampleApp.exe;#1 -out:SampleApp.exe.manifest
注意這個地方的1是manifest的資源序號懊渡,默認情況下是1。關(guān)于mt.exe的更多命令可以執(zhí)行mt.exe /?
或參考這里军拟。
方法二:
使用mt.exe工具嵌入剃执,如果采用VS構(gòu)建應(yīng)用程序,使用這種方式需要先關(guān)閉VS默認嵌入manifest的行為:
Properties > Configuration Properties > Linker > Manifest File > Generate Manifest > No (/MANIFEST:NO)
然后在Properties > Configuration Properties > Build Events > Post Build Event
中寫入以下命令:
mt.exe -manifest SampleApp.exe.manifest -outputresource:$(OutDir)$(TargetName)$(TargetExt);#1
上面命令的前提是文件SampleApp.exe.manifest在你的工程目錄下懈息。重新編譯肾档,鏈接。想知道m(xù)anifest是否成功寫入SampleApp.exe中辫继,同樣可以采用上面說的兩種方法進行驗證怒见。
5. 實例應(yīng)用
再次回到我們面臨問題,仍然以應(yīng)用程序SampleApp.exe為例姑宽,假設(shè)SampleApp依賴libA.dll遣耍,而libA.dll又依賴于libB.dll。我們希望獎libA.dll和libB.dll放到目錄SampleAssembly下炮车,目錄結(jié)構(gòu)如下:
/AppDir
|---SampleApp.exe
| |
|---SampleAssembly
|---SampleAssembly.manifest
|---libA.dll
|---libB.dll
1. 按照第4小節(jié)所述的方法將Sample.exe.manifest嵌入到Sample.exe中
2. 在SampleApp.exe所處目錄下新建一個目錄舵变,目錄名同依賴程序集名(SampleAssembly)
3. 在SampleAssembly目錄下,新建一個manifest文件瘦穆,文件名同程序集名(SampleAssembly),其內(nèi)容如下:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32" name="SampleAssembly" version="1.0.0.0" processorArchitecture="x86"/>
<file name="libA.dll"/>
<file name="libB.dll"/>
</assembly>
4. 將libA.dll, libB.dll放到SampleAssembly目錄下
5. 運行SampleApp.exe纪隙,看是否能正常工作了呢
6. 更多參考
[1]. https://msdn.microsoft.com/en-us/library/windows/desktop/ff951640(v=vs.85).aspx
[2]. https://msdn.microsoft.com/en-us/library/windows/desktop/aa374224(v=vs.85).aspx
[3]. https://msdn.microsoft.com/en-us/library/windows/desktop/aa374219(v=vs.85).aspx
[4]. https://msdn.microsoft.com/en-us/library/windows/desktop/aa374191(v=vs.85).aspx
[5]. https://docs.microsoft.com/zh-cn/cpp/build/manifest-generation-in-visual-studio
[6]. https://docs.microsoft.com/zh-cn/cpp/build/how-to-embed-a-manifest-inside-a-c-cpp-application
[7]. https://docs.microsoft.com/zh-cn/cpp/build/reference/manifestdependency-specify-manifest-dependencies
[8]. https://en.wikipedia.org/wiki/DLL_Hell
[9]. https://msdn.microsoft.com/en-us/library/windows/desktop/aa375649(v=vs.85).aspx