通过清单的DLL重定向加载DLL文件两次
我在我的 Visual C++ DLL 文件项目中包含了 python.h
,这导致它自动链接到了 python25.dll
。不过,我想加载一个特定的 python25.dll
(因为电脑上可能有多个这样的文件),所以我创建了一个非常简单的清单文件,叫做 test.manifest:
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<file name="python25.dll" />
</assembly>
然后,我通过以下方式将它与 Visual Studio 自动生成的嵌入式清单文件合并:
Configuration Properties -> Manifest Tool -> Input and Output -> Additional Manifest Files
-->$(ProjectDir)\src\test.manifest
现在 python25.dll
被加载了两次:一次是根据清单文件请求的,另一次是 Windows 按照它的搜索顺序找到的。
这是 Process Explorer 的截图 http://dl.dropbox.com/u/3545118/python25_dll.png
为什么会这样?我该如何只加载清单文件指向的 DLL 文件呢?
4 个回答
Dependency Walker 通常是解决这类问题的最佳工具。不过,我不太确定它对清单文件的处理效果如何...
在这团乱麻中,实际的可执行文件在哪里呢?
我想到两种可能性:
你正在写一个 Python 扩展的 DLL 文件。所以 Python 进程会加载你的 DLL 文件,而它本身就已经有了 python25.dll 的依赖。
加载你 DLL 文件的 EXE 文件是用 DLL 文件项目提供的头文件和库构建的。因此,它会从你的头文件中继承
#pragma comment(lib,"python25.lib")
,结果就是加载了你的 DLL 文件。
我对第二种情况的问题是,我本来期待 EXE 文件和你的 DLL 文件应该在同一个文件夹里,因为如果 EXE 文件是隐式加载你的 DLL 文件的话。在这种情况下,EXE 文件、你的 DLL 文件和 python25.dll 都已经在同一个文件夹里。那为什么系统32版本还会被加载呢?隐式加载的 DLL 文件的搜索顺序总是先在应用程序 EXE 文件的文件夹里。
所以你提问中隐含的有趣问题是:系统32 的 python26.dll 是怎么被加载的呢?
我对这个问题有了一些新的理解。
首先让我澄清一下情况:
- 我正在制作一个DLL文件,这个文件可以嵌入并扩展Python,使用的是Python的C API和Boost.Python库。
- 因此,我在我的DLL文件所在的文件夹里提供了一个
python25.dll
文件,还有一个boost_python-vc90-mt-1_39.dll
文件。 - 接着,我有一个EXE文件,这是一个演示程序,用来展示如何链接到我的DLL文件:这个EXE文件不一定要和我的DLL文件放在同一个文件夹,只要DLL文件能在系统的路径中找到就行(我假设最终用户可能会把它放在不同的文件夹里)。
然后,当我运行这个EXE文件时,当前的工作目录并不是包含python25.dll
的那个文件夹,这就是为什么搜索顺序会导致找到其他的python25.dll
,而不是我的那个。
现在我发现使用清单技术是个不错的办法:我成功地把加载指向了“我的”python25.dll
。
问题是,导致“重复”加载的正是这个boost_python-vc90-mt-1_39.dll
文件!
如果我不加载这个文件,那么python25.dll
就能正确地被重定向。现在我需要想办法告诉Boost DLL文件不要加载另一个python25.dll
……
在与WinSxS和DLL重定向的漫长斗争后,我给你一些建议:
一些背景知识
在Windows中,有很多原因会导致DLL文件被加载:
- 显式链接(
LoadLibrary
)——加载器会使用正在运行的EXE文件的当前激活上下文。这很直观。 - 隐式链接(“加载时链接”,也就是“自动”链接)——加载器会使用依赖的DLL文件的默认激活上下文。比如,如果
A.exe
依赖于B.dll
,而B.dll
又依赖于C.dll
(都是隐式链接),那么在加载C.dll
时,加载器会使用B.dll
的激活上下文。记得的话,这意味着如果B的DllMain
加载了C.dll
,它可能会使用B.dll
的激活上下文——大多数情况下,这意味着系统范围的默认激活上下文。所以你会从%SystemRoot%
获取你的Python DLL。 - COM(
CoCreateInstance
)——这是个麻烦的地方。非常微妙。实际上,加载器可以通过COM从注册表中查找DLL文件的完整路径(在HKCR\CLSID
下)。如果用户给了完整路径,LoadLibrary
就不会进行任何搜索,因此激活上下文无法影响DLL文件的解析。可以通过comClass
元素等进行重定向,具体见[参考][msdn_assembly_ref]。 - 即使你有正确的清单,有时仍然有人可以在运行时使用激活上下文API来更改激活上下文。如果是这种情况,通常你无能为力(见下面的终极解决方案);这只是为了完整性。如果你想找出是谁在捣乱激活上下文,可以用WinDbg的
bp kernel32!ActivateActCtx
。
现在来找出罪魁祸首
- 找出导致DLL文件加载的原因最简单的方法是使用进程监视器。你可以监视“路径包含
python25.dll
”或“详细信息包含python25.dll
”(用于COM查找)。双击一个条目会显示调用栈(你需要先设置符号搜索路径,并设置微软的PDB服务器)。这应该能满足你大部分的需求。 - 有时从上面获得的调用栈可能是从新线程中产生的。为此你需要WinDbg。这可以是另一个话题,但简单来说,你可以使用
sxe ld python25
来查看其他线程在做什么(!findstack MyExeModuleName
或~*k
),从而导致DLL文件的加载。
现实世界的解决方案
与其纠结于这个WinSxS的事情,不如试试用Mhook或EasyHook来钩住LoadLibraryW
。你可以完全用自己的逻辑替代这个调用。你可以在午餐前完成这个,并重新找到生活的意义。
[msdn_assembly_ref]: 程序集清单