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

发布于 2024-08-19 12:15:50 字数 993 浏览 6 评论 0原文

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

Process Explorer 的屏幕转储 http://dl.dropbox.com/u/3545118/python25_dll.png

为什么会发生这种情况?我怎样才能加载清单指向的 DLL 文件?

I'm including python.h in my Visual C++ DLL file project which causes an implicit linking with python25.dll. However, I want to load a specific python25.dll (several can be present on the computer), so I created a very simple manifest file named 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>

And I'm merging it with the automatically embedded manifest file generated by Visual Studio thanks to:

Configuration Properties -> Manifest Tool -> Input and Output -> Additional Manifest Files
-->$(ProjectDir)\src\test.manifest

python25.dll is now loaded twice: the one requested by the manifest, and the one that Windows should find through its search order.

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

Why is that happening and how can I just load the DLL file pointed by the manifest?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

梦里的微风 2024-08-26 12:15:50

经过与 WinSxS 和 DLL 重定向的详尽战斗后,这是我的给您的建议:

一些背景知识

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

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

现在开始寻找罪魁祸首

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

现实世界的解决方案

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

[msdn_ assembly_ref]:程序集清单

After exhaustive battle with WinSxS and DLL redirection, here's my advice for you:

Some background

Various things can cause a DLL file to be loaded under Windows:

  • Explicit linking (LoadLibrary) -- the loader uses the current activation context of the running EXE file. This is intuitive.
  • Implicit linking ("load time linkage", the "auto" ones) -- the loader uses the default activation context of the depending DLL file. If A.exe depends on B.dll depends on C.dll (all implicit linkage), the loader will use B.dll's activation context when loading C.dll. IIRC, it means if B's DllMain loads C.dll, it can be using B.dll's activation context -- most of the time it means the system-wide default activation context. So you get your Python DLL from %SystemRoot%.
  • COM (CoCreateInstance) -- this is the nasty one. Extremely subtle. It turns out the loader can look up the full path of a DLL file from the registry using COM (under HKCR\CLSID). LoadLibrary will not do any searching if the user gives it a full path, so the activation context can't affect the DLL file resolution. Those can be redirected with the comClass element and friends, see [reference][msdn_assembly_ref].
  • Even though you have the correct manifest, sometimes someone can still change the activation context at run time using the Activation Context API. If this is the case, there is usually not much you can do about it (see the ultimate solution below); this is just here for completeness. If you want to find out who is messing with the activation context, WinDbg bp kernel32!ActivateActCtx.

Now on to finding the culprit

  1. The easiest way to find out what causes a DLL file to load is to use Process Monitor. You can watch for "Path containing python25.dll" or "Detail containing python25.dll" (for COM lookups). Double clicking an entry will actually show you a stack trace (you need to set the symbol search paths first, and also set Microsoft's PDB server). This should be enough for most of your needs.
  2. Sometimes the stack trace obtained from above could be spawned from a new thread. For this purpose you need WinDbg. That can be another topic, but suffice to say you can sxe ld python25 and look at what other threads are doing (!findstack MyExeModuleName or ~*k) that causes a DLL file to load.

Real world solution

Instead of fiddling with this WinSxS thing, try hooking LoadLibraryW using Mhook or EasyHook. You can just totally replace that call with your custom logic. You can finish this before lunch and find the meaning of life again.

[msdn_assembly_ref]: Assembly Manifests

情话难免假 2024-08-26 12:15:50

我对这个问题的理解取得了一些进展。

首先让我澄清一下场景:

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

然后,当运行 EXE 文件时,当前目录不是包含 python25.dll 的目录,这就是为什么使用搜索顺序,并且可以使用其他一些 python25.dll在我之前发现的。

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

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

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

I made some progress for the understanding of the issue.

First let me clarify the scenario:

  • I'm building a DLL file that both embeds and extends Python, using the Python C API and Boost.Python.
  • Thus, I'm providing a python25.dll in the same folder as my DLL file, as well as a boost_python-vc90-mt-1_39.dll.
  • Then I have an EXE file which is a demo to show how to link to my DLL file: this EXE file doesn't have to be in the same folder as my DLL file, as long as the DLL file can be found in the PATH (I'm assuming that the end user may or may not put it in the same folder).

Then, when running the EXE file, the current directory is not the one containing python25.dll, and that's why the search order is used and some other python25.dll can be found before mine.

Now I figured out that the manifest technique was the good approach: I managed to redirect the loading to "my" python25.dll.

The problem is that this is the Boost DLL file boost_python-vc90-mt-1_39.dll that's responsible for the "double" loading!

If I don't load this one, then python25.dll is correctly redirected. Now I somehow have to figure out how to tell the Boost DLL file not to load another python25.dll...

喜爱皱眉﹌ 2024-08-26 12:15:50

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 都已位于同一文件夹中。那么为什么会加载 system32 版本呢?隐式加载的 DLL 文件的搜索顺序始终位于应用程序 EXE 文件的文件夹中。

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

Dependency Walker is usually the best tool for resolving this kind of problem. I'm not too sure how well it handles manifests though...

Where in this entangled mess is the actual process executable file?

Two possibilities come to mind:

  1. You are writing a Python extension DLL file. So the Python process is loading your DLL file, and it would already have its own python25.dll dependency.

  2. The EXE file loading your DLL file is being built with header files and libraries provided by the DLL file project. So it is inheriting the #pragma comment(lib,"python25.lib") from your header file and as a result is loading the DLL file itself.

My problem with the second scenario is, I'd expect the EXE file, and your DLL file, to be in the same folder in the case that the EXE file is implicitly loading your DLL file. In which case the EXE file, your DLL file and the python25.dll are all already in the same folder. Why then would the system32 version ever be loaded? The search order for implicitly loaded DLL files is always in the application EXE file's folder.

So the actual interesting question implicit in your query is: How is the system32 python26.dll being loaded at all?

℡Ms空城旧梦 2024-08-26 12:15:50

最近,我遇到了一个非常类似的问题

  1. :应用程序嵌入 Python 从已知位置加载 python32.dll,即 并排程序集 (WinSxS)Python.manifest
  2. 尝试在嵌入式 Python 解释器内导入 tkinter 导致第二次加载完全相同的内容python32.dll,但位于不同的非默认地址
  3. tkinter 模块(具体来说,_tkinter.pyd)的初始化函数失败,因为 Python 解释器线程状态无效(_PyThreadState_Current == NULL)。显然,从重复的 python32.dll 加载的第二个 Python 解释器从未调用过 Py_Initialize()。

为什么 python32.dll 被加载两次?正如我在 我关于 python-capi 的帖子中解释的那样,这是由于应用程序从 WinSxS 加载 python32.dll,但 _tkinter.pyd 无法识别该程序集,因此使用常规 DLL 搜索路径加载 python32.dll。

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

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

Recently, I hit a very similar problem:

  1. My application embedding Python loads the python32.dll from a known location, that is a side-by-side assembly (WinSxS) with Python.manifest
  2. Attempt to import tkinter inside the embedded Python interpreter caused second loading of the very same python32.dll, but under a different non-default address.
  3. The initialisation function of tkinter module (specifically, _tkinter.pyd) was failing because to invalid Python interpreter thread state (_PyThreadState_Current == NULL). Obviously, Py_Initialize() was never called for the second Python interpreter loaded from the duplicate python32.dll.

Why was the python32.dll loaded twice? As I explained in my post on python-capi, this was caused by the fact the application was loading python32.dll from WinSxS, but _tkinter.pyd did not recognise the assembly, so python32.dll was loaded using the regular DLL search path.

The Python.manifest + python32.dll assembly was recognised by the DLL loading machinery as a different module (under different activation context), than the python32.dll requested by _tkinter.pyd.

Removing the reference to Python.manifest from the application embedding Python and allowing the DLL search path to look for the DLLs solved the problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文