同名的非托管 C++ DLL 共存于同一进程中

Posted

技术标签:

【中文标题】同名的非托管 C++ DLL 共存于同一进程中【英文标题】:Unmanaged C++ DLL's with same name coexisting in same process 【发布时间】:2013-01-11 15:17:36 【问题描述】:

使用 Visual Studio c++ V10,我试图弄清楚如何构建 DLL 并解决 DLL 命名冲突。以下是详细信息。

公司 S 运送了一个名为 M.EXE 的产品。假设M.EXE 安装在\S\BIN\M.EXE 中。公司 S 静态链接到一个名为 U.DLL 的 DLL,该 DLL 安装在 \S\BIN\U.DLL 中。 U.DLL 包含开源代码,并使用 Visual C++ 编译器选项 /Zc:wchar_t- 构建,它不将 wchar 识别为本机类型。

C 公司发布了一个名为 O.DLL 的 DLL,并发布了该 DLL 的 API,并发布了一个用于 O.DLL 的导入库。假设O.DLL 安装在\C\BIN\O.DLL 中。 O.DLL 静态链接到名为U.DLL 的DLL,该DLL 安装在\C\BIN\U.DLL 中。 U.DLL 基于相同的开源代码构建,但使用 Visual C++ 编译器选项 /Zc:wchar_t 构建,它确实将 wchar_t 识别为本机类型。

理想情况下,C 公司和 S 公司会同意使用相同的 Visual C++ 选项构建U.DLL,但这是不可能的。

来自 S 公司的M.EXE 是可扩展的,因为我可以在非托管 C++ 中构建我自己的 DLL,称之为NODE.DLL,如果我正确设置了所有内容,M.EXE 将调用它。我想构建NODE.DLL,以便它静态链接到来自C公司的O.DLL。但问题是一旦M.EXE运行,它已经从\S\BIN加载了U.DLL库,以及来自\S\BIN的符号\S\BIN\U.DLL\C\BIN\U.DLL 中的略有不同,因为 U.DLL 是由每个公司构建的。因此,当M.EXE 尝试加载NODE.DLL 时,它会失败,因为当NODE.DLL 加载需要O.DLLO.DLL 时,\C\BIN\U.DLL 所需的符号不存在,因为Windows 已经看到U.DLL正在加载中。

情况示意图如下:

M.EXE static link to -> \S\BIN\U.DLL
M.EXE dynamic link to -> NODE.DLL
NODE.DLL static link to  O.DLL
O.DLL static link to \C\BIN\U.DLL

实际上,我需要\S\BIN\U.DLL\C\BIN\U.DLL 共存于同一进程空间中,并让M.EXE 使用其版本的U.DLLO.DLL 使用其版本的U.DLL

请注意,我无法重新构建 M.EXEO.DLL 来更改它们各自加载 U.DLL 的方式。它们来自第三方,因此无法更改静态链接。我也没有在O.DLL 上使用LoadLibrary 的选项,因为它是一个C++ 库,提供了一个导入库。

我相信可以使用清单,这样当我构建静态链接到 O.DLL 的 NODE.DLL 时,我会在 NODE.DLL 的清单中进行设置,以便 O.DLL 加载它自己的 @987654372 副本@ 安装在\C\BIN\U.DLL 中。我只是无法弄清楚如何做到这一点。理想情况下,我不想修改 O.DLL 的清单,但如果这是唯一的解决方案,我会接受。

【问题讨论】:

DLL 的,DLL 的无处不在! (对不起,忍不住)你好,欢迎。 基本上问题在于其 IAT 中的O.DLL 指定它与U.DLL 链接,不合格。 【参考方案1】:

您可以在同一个进程中拥有多个具有相同文件名的 DLL,方法是使用绝对路径加载其中的一个或多个。这确实需要动态加载 DLL,但其他方面的行为是相同的。

您需要std::string moduleName = appPath + "\s\bin\u.dll"; LoadModule(moduleName.c_str()),而不是在构建过程中进行链接。因为这对于需要加载哪个 DLL 是明确的,所以它允许您加载具有“相同”名称的多个。

加载模块后,您可以将每个必要的函数分配给函数指针,然后将它们包装起来或使用合法但很少使用的syntax of calling functions pointers as normal functions (funcPtr(params))。

如果您使用的是较新版本的 Windows,则可以使用 DLL 清单来加强模块周围的版本控制/命名,并导致 EXE 加载与通常情况不同的 DLL。我不熟悉这究竟是如何完成的,尽管它记录在 MSDN 上(也可能在这里)。

【讨论】:

这不是一个选项,因为我没有能力重建 M.EXE 或 O.DLL 。它们来自不同的第三方。 如果其中一个模块使用部分路径或绝对路径,或者其中一个模块使用动态加载,那么您可能会过得去。否则,清单将是你唯一的机会。 我相信清单是唯一的选择。我想知道在清单中放入什么才能使事情正常进行。 M.EXE 和 O.DLL 都是使用导入库构建的,然后使用正常的 DLL 加载规则加载 U.DLL。每个公司都将自己的 U.DLL 版本放在自己的 BIN 目录中。当 M.EXE 运行时,它会拾取它的 U.DLL 副本。当你编写一个使用 O.DLL 的标准 EXE 时,它会选择它的 U.DLL 副本。【参考方案2】:

您可以在运行时使用 /delayload 链接器选项(VS 项目属性中的链接器/输入/延迟加载的 DLL)和自定义挂钩以编程方式解析源 DLL。在您的一个源文件中,您需要定义和注册一个延迟加载挂钩函数。在钩子函数的 dliNotePreLoadLibrary 通知处理程序中,只需使用所需 DLL 的显式路径调用 LoadLibrary,然后将 DLL 的 HMODULE 传递回延迟加载代码。 delayload 代码会将导入的函数解析为您提供给它的 DLL,无论是否已将另一个同名 DLL 加载到进程中。

在我的脑海中,你的钩子看起来像这样(其中 MyCustomLoadLibrary 需要替换为调用 LoadLibrary 的代码,在冲突的情况下使用所需 DLL 的完整路径,或者只是不合格的文件名):

#include <delayimp.h>
FARPROC WINAPI MyDliNotifyHook( unsigned dliNotify, PDelayLoadInfo pdli )

  if( dliNotify == dliNotePreLoadLibrary )
    return (FARPROC)MyCustomLoadLibrary( pdli->szDll );
  return NULL;

extern "C" PfnDliHook __pfnDliNotifyHook2 = MyDliNotifyHook;

【讨论】:

这仅在 NODE.DLL 是链接到冲突 DLL 的情况下才有效。重新阅读您的问题后,我看到您说它是链接到冲突 DLL 的依赖 DLL 之一。在这种情况下,您必须以编程方式解析依赖 DLL 的导入地址表。这并不难做到,但是这里描述的步骤太复杂了。 '以编程方式解析依赖DLL的导入地址表':能否指点一下,我能看懂更多吗? 有两种情况:加载程序可以成功解析所有导入的情况(尽管是错误的DLL);以及加载程序无法解析导入的情况。如果你的情况是前者,我可以提供一些指示。如果您的情况是后者,您必须从根本上重现 Windows 加载程序代码(解析导入并应用修复,然后调用入口点函数)。很少有宝贵的文件,我不知道任何公开的信息来源。这不适合胆小的人,但有可能:我已经成功地做到了。 “如果你的情况是前者,我可以提供一些指点”。是的,请先生。 哈珀,我刚刚意识到你不是 OP。由于这与 OP 无关,请开始您自己的问题。【参考方案3】:

尝试使用 LoadLibrary 和 GetProcAddress。这将需要重组您的代码以在任何地方使用函数指针。见:

MSDN web page for LoadLibrary

【讨论】:

这不是一个选项,因为我没有能力重建 M.EXE 或 O.DLL 。它们来自不同的第三方。【参考方案4】:

你很幸运 U.DLL 是开源的。您需要构建一个同时支持/Zc:wchar_t-/Zc:wchar_t 函数的版本。第一个选项仅将wchar_t- 定义为unsigned short。对于每个没有wchar_t 参数的函数,您将收到大量重复符号的链接器警告,否则您最终会得到一个更胖的 DLL。

请注意,如果有任何全局变量或 static 变量使用 wchar_t,那么您也将拥有这些变量的两个副本。但是,如果您在一个进程中硬塞两个 U.DLL 副本,您将获得相同的效果。

【讨论】:

以上是关于同名的非托管 C++ DLL 共存于同一进程中的主要内容,如果未能解决你的问题,请参考以下文章

WCF 服务调用具有复杂数据类型的非托管 c++ dll

调用使用 C# 返回结构数组的非托管 dll 函数

将非托管 C++ dll 添加到托管 C++ dll

如何添加对由 C# 项目调用的非托管 C++ 项目的引用?

如何在非托管 c++ dll 中查找调用方程序集名称

在 c++ 中为非托管 c# dll 使用 std::string