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

时间:2010-01-27 14:45:31

标签: c++ python visual-studio dll manifest

我在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应该通过搜索顺序找到的那个。

Screendump of Process Explorer http://dl.dropbox.com/u/3545118/python25_dll.png

为什么会发生这种情况?如何加载清单指向的DLL文件?

4 个答案:

答案 0 :(得分:6)

WinSxS和DLL重定向进行彻底的争斗后,这是我对你的建议:

一些背景

各种事情都可能导致在Windows下加载DLL文件:

  • 显式链接(LoadLibrary) - 加载程序使用正在运行的EXE文件的当前激活上下文。这很直观。
  • 隐式链接(“加载时间链接”,“自动”链接) - 加载程序使用依赖DLL文件默认激活上下文。如果A.exe取决于B.dll,则取决于C.dll(所有隐式链接),加载器将在加载B.dll时使用C.dll的激活上下文。 IIRC,这意味着如果B DllMain加载C.dll,它可以使用B.dll的激活上下文 - 大多数时候它意味着系统范围的默认激活上下文。所以你从%SystemRoot%获得了你的Python DLL。
  • COM(CoCreateInstance) - 这是令人讨厌的。非常微妙。事实证明,加载程序可以使用COM(在HKCR\CLSID下)从注册表中查找DLL文件的完整路径。如果用户给它一个完整路径,LoadLibrary将不会进行任何搜索,因此激活上下文不会影响DLL文件解析。这些可以使用comClass元素和朋友重定向,请参阅[reference] [msdn_assembly_ref]。
  • 即使您拥有正确的清单,有时仍有人可以使用Activation Context API在运行时更改激活上下文。如果是这种情况,通常没有什么可以做的(参见下面的最终解决方案);这只是为了完整性。如果你想知道谁在搞乱激活上下文,WinDbg bp kernel32!ActivateActCtx

现在找到罪魁祸首

  1. 找出导致DLL文件加载的最简单方法是使用Process Monitor。您可以关注“路径包含python25.dll”或“详细信息包含python25.dll”(用于COM查找)。双击条目实际上会显示堆栈跟踪(您需要先设置符号搜索路径,还要设置Microsoft的PDB服务器)。这应该足以满足您的大多数需求。
  2. 有时从上面获得的堆栈跟踪可以从新线程生成。为此,您需要WinDbg。这可能是另一个主题,但足以说你可以sxe ld python25并查看导致DLL文件加载的其他线程正在做什么(!findstack MyExeModuleName~*k)。
  3. 真实世界的解决方案

    尝试使用MhookEasyHook挂钩LoadLibraryW,而不是摆弄这个WinSxS。您可以使用自定义逻辑完全替换该调用。你可以在午餐前完成这项工作,再次找到生命的意义。

    [msdn_assembly_ref]: Assembly Manifests

答案 1 :(得分:2)

我在理解这个问题方面取得了一些进展。

首先让我澄清一下情景:

  • 我正在使用Python C API和Boost.Python构建一个嵌入和扩展Python的DLL文件。
  • 因此,我在与我的DLL文件相同的文件夹中提供python25.dll,以及boost_python-vc90-mt-1_39.dll
  • 然后我有一个EXE文件,它是一个演示如何链接到我的DLL文件:这个EXE文件不必与我的DLL文件在同一个文件夹中,只要找到DLL文件在PATH中(我假设最终用户可能会也可能不会将它放在同一个文件夹中)。

然后,在运行EXE文件时,当前目录不是包含python25.dll的目录,这就是使用搜索顺序的原因,并且在我之前可以找到其他python25.dll

现在我发现清单技术是一种很好的方法:我设法将加载重定向到“我的”python25.dll

问题是这是Boost DLL文件boost_python-vc90-mt-1_39.dll负责“双重”加载!

如果我没有加载这个,那么python25.dll被正确重定向。现在我不得不弄清楚如何告诉Boost DLL文件不要加载另一个python25.dll ......

答案 2 :(得分:1)

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

在这个纠结的混乱中,实际的流程可执行文件是什么?

有两种可能性浮现在脑海中:

  1. 您正在编写Python扩展DLL文件。所以Python进程正在加载你的DLL文件, 已经有了自己的python25.dll依赖。

  2. 正在使用DLL文件项目提供的头文件和库构建加载DLL文件的EXE文件。所以它从头文件继承#pragma comment(lib,"python25.lib"),结果就是加载DLL文件。

  3. 第二种情况的问题是,在EXE文件隐式加载DLL文件的情况下,我希望EXE文件和您的DLL文件位于同一文件夹中。在这种情况下,EXE文件,您的DLL文件和python25.dll都已存在于同一文件夹中。为什么然后会加载system32版本?隐式加载的DLL文件的搜索顺序始终位于应用程序EXE文件的文件夹中。

    因此,查询中隐含的实际有趣问题是:system32 python26.dll是如何加载的?

答案 3 :(得分:0)

最近,我遇到了非常similar problem

  1. 我的应用程序嵌入Python从已知位置加载 python32.dll ,这是side-by-side assembly (WinSxS) Python.manifest
  2. 在嵌入式Python解释器中尝试import tkinter导致第二次加载相同的 python32.dll ,但在不同的非默认地址下。< / LI>
  3. tkinter模块的初始化功能(特别是 _tkinter.pyd )失败,因为无效的Python解释器线程状态(_PyThreadState_Current == NULL)。显然,Py_Initialize()从未被调用从复制的python32.dll加载的第二个Python解释器。
  4. 为什么python32.dll加载了两次?正如我在my post on python-capi中解释的那样,这是因为应用程序从WinSxS加载python32.dll,但 _tkinter.pyd 无法识别程序集,因此使用python32.dll加载常规DLL搜索路径。

    Python.manifest + python32.dll程序集被DLL加载机制识别为一个不同的模块(在不同的激活上下文下),而不是_tkinter.pyd请求的python32.dll。

    从嵌入Python的应用程序中删除对Python.manifest的引用,并允许DLL搜索路径查找DLL解决了这个问题。