同名的非托管 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.DLL
的O.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.DLL
和O.DLL
使用其版本的U.DLL
。
请注意,我无法重新构建 M.EXE
或 O.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 共存于同一进程中的主要内容,如果未能解决你的问题,请参考以下文章