【问题标题】:DLL file loaded twice with DLL redirection through manifestDLL 文件通过清单加载了两次 DLL 重定向
【发布时间】:2010-01-27 14:45:31
【问题描述】:

我将python.h 包含在我的Visual C++ DLL 文件项目中,这会导致与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 文件?

【问题讨论】:

  • 隐式加载是如何完成的? python标头中是否有#pragma comment( lib xxx )
  • 是的,由于#pragma 评论,链接已完成。
  • 只是出于好奇,如果删除syswow64中的python25.dll会怎样?
  • 感谢测试思路,结果很有意思!现在我的“好” dll,即清单所指向的那个,本身被加载了两次!!!
  • 您的应用程序和两个 python25.dll 图像都是 32 位的,对吗?您可以查看“图像类型”列。也许这是 wow64 的某种怪癖。

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


【解决方案1】:

在与WinSxS 和 DLL 重定向进行了彻底的战斗之后,我给你的建议是:

一些背景

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

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

现在开始寻找罪魁祸首

  1. 找出导致加载 DLL 文件的原因的最简单方法是使用 Process Monitor。您可以查看“包含python25.dll”的“Path”或“包含python25.dll”的“Detail”(用于 COM 查找)。双击一个条目实际上会显示一个堆栈跟踪(您需要先设置符号搜索路径,还要设置 Microsoft 的 PDB 服务器)。这应该足以满足您的大部分需求。
  2. 有时从上面获得的堆栈跟踪可能是从新线程中产生的。为此,您需要WinDbg。这可能是另一个话题,但我只想说您可以sxe ld python25 并查看导致加载 DLL 文件的其他线程(!findstack MyExeModuleName~*k)正在做什么。

现实世界的解决方案

不要摆弄这个 WinSxS 的东西,尝试使用 MhookEasyHook 挂钩 LoadLibraryW。您可以用您的自定义逻辑完全替换该调用。你可以在午饭前完成这个,重新找到生命的意义。

[msdn_assembly_ref]: Assembly Manifests

【讨论】:

    【解决方案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...

    【讨论】:

    • 我设法用“embed-manifest=off”选项重新编译了 boost.python 库。奇迹! “python25.dll”不再加载两次。但是现在,每当我从我的 C++ 代码中导入一个 pyd 扩展时,它就会再次加载两次......没完没了的问题......
    • 尝试对boost_python-vc90-mt-1_39.dll 执行相同的清单劫持——例如将清单文件放在其旁边,或使用mt 更改它。这就是我的印象。 我没试过这个,我也在用 WinSxS 挣扎.
    【解决方案3】:

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

    在这个纠缠不清的混乱中,实际的进程可执行文件在哪里?

    想到两种可能性:

    1. 您正在编写 Python 扩展 DLL 文件。因此,Python 进程正在加载您的 DLL 文件,并且它已经拥有自己的 python25.dll 依赖项。

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

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

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

    【讨论】:

    • 谢谢克里斯,我在我发布的答案中澄清了这个场景。现在我发现问题是由 boost.python dll 引起的...
    • 不知何故,你的 dll 的激活上下文没有被 boost 使用 - 你是在做 boost 的静态加载还是动态加载?和 python dll? boost dll 是否有自己的清单可以重置 AC 和/或以某种方式显式引用 system32 中的 python dll?
    【解决方案4】:

    最近,我打了一个很similar problem

    1. 我嵌入 Python 的应用程序从已知位置加载 python32.dll,即带有 Python.manifestside-by-side assembly (WinSxS)
    2. 在嵌入式 Python 解释器中尝试 import tkinter 导致第二次加载完全相同的 python32.dll,但在不同的非默认地址下
    3. tkinter 模块(特别是 _tkinter.pyd)的初始化函数失败,因为 Python 解释器线程状态无效 (_PyThreadState_Current == NULL)。显然,从未为从重复的 python32.dll 加载的第二个 Python 解释器调用 Py_Initialize()

    为什么 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 解决了问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-25
      • 1970-01-01
      • 2010-10-20
      • 2019-07-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多