通过清单的DLL重定向加载DLL文件两次

10 投票
4 回答
3788 浏览
提问于 2025-04-15 18:32

我在我的 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 个回答

1

Dependency Walker 通常是解决这类问题的最佳工具。不过,我不太确定它对清单文件的处理效果如何...

在这团乱麻中,实际的可执行文件在哪里呢?

我想到两种可能性:

  1. 你正在写一个 Python 扩展的 DLL 文件。所以 Python 进程会加载你的 DLL 文件,而它本身就已经有了 python25.dll 的依赖。

  2. 加载你 DLL 文件的 EXE 文件是用 DLL 文件项目提供的头文件和库构建的。因此,它会从你的头文件中继承 #pragma comment(lib,"python25.lib"),结果就是加载了你的 DLL 文件。

我对第二种情况的问题是,我本来期待 EXE 文件和你的 DLL 文件应该在同一个文件夹里,因为如果 EXE 文件是隐式加载你的 DLL 文件的话。在这种情况下,EXE 文件、你的 DLL 文件和 python25.dll 都已经在同一个文件夹里。那为什么系统32版本还会被加载呢?隐式加载的 DLL 文件的搜索顺序总是先在应用程序 EXE 文件的文件夹里。

所以你提问中隐含的有趣问题是:系统32 的 python26.dll 是怎么被加载的呢?

3

我对这个问题有了一些新的理解。

首先让我澄清一下情况:

  • 我正在制作一个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……

9

在与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

现在来找出罪魁祸首

  1. 找出导致DLL文件加载的原因最简单的方法是使用进程监视器。你可以监视“路径包含python25.dll”或“详细信息包含python25.dll”(用于COM查找)。双击一个条目会显示调用栈(你需要先设置符号搜索路径,并设置微软的PDB服务器)。这应该能满足你大部分的需求。
  2. 有时从上面获得的调用栈可能是从新线程中产生的。为此你需要WinDbg。这可以是另一个话题,但简单来说,你可以使用sxe ld python25来查看其他线程在做什么(!findstack MyExeModuleName~*k),从而导致DLL文件的加载。

现实世界的解决方案

与其纠结于这个WinSxS的事情,不如试试用MhookEasyHook来钩住LoadLibraryW。你可以完全用自己的逻辑替代这个调用。你可以在午餐前完成这个,并重新找到生活的意义。

[msdn_assembly_ref]: 程序集清单

撰写回答