您可以拥有多个具有通用导出/序数的 DLL 并在运行时进行交换吗?
Posted
技术标签:
【中文标题】您可以拥有多个具有通用导出/序数的 DLL 并在运行时进行交换吗?【英文标题】:Can you have multiple DLLs with common exports/ordinals and exchange at run-time? 【发布时间】:2022-01-09 20:17:50 【问题描述】:我正在尝试创建几个具有不同实现但符号导出一致的 Win32 64 位 DLL (Windows 10)。这样做的目的是在构建时与任何一个链接,但在部署时可以选择安装任何一个 DLL 并使用它正确运行。我在 Linux 上直接实现了这一点,我对运行时链接更加熟悉和熟悉。但是在 Windows 上,我还没有做到这一点,我想知道这是否可能。我正在尝试同时使用 VS2010 和 VS2019。
假设我有两个库 blah_legacy.dll 和 blah_modern.dll。它们都导出 6 个符号,这些符号是使用库的接口,例如blah_open、blah_read、blah_write、blah_close、blah_control、blah_status。
我可以为 blah 实现和调用每个符号的测试程序链接导入库,并使用相应的 blah DLL 正确加载和执行。
但是,我还不能在运行时切换 DLL。例如,如果我将其重命名为 blah-legacy.dll,我是否真的能够与 blah-legacy.lib 链接,然后使用 blah-modern.dll 运行? (反之亦然。)
我已经解决了基本的文件命名问题,并确保可以实际找到所需的 DLL。我仍然收到应用程序无法启动 (0x22)。
我在 DLL 上使用了“objdump -xs”,注意到符号的顺序和它们的序号不同。所以我创建了一个 .def 文件并确保导出的符号在数量、名称和序数上匹配。仍然没有 - 发生同样的错误。
对此我显然还没有弄清楚,希望得到一些指导。这真的可能吗?我从哪里开始寻找(哪些工具)以确定下一步要采取的步骤。
【问题讨论】:
你为什么不编写 DLLblah.dll
来使用LoadLibrary()
加载两个 DLL?您将使用GetProcAddress()
在两个DLL 中获得指向blah_open()
的指针,并将指针存储在blah_open_legacy
和blah_open_modern
中。然后,blah.dll
中的函数 blah_open()
将调用 blah_open_legacy()
或 blah_open_modern()
,具体取决于您使用“开关”选择的任何 DLL。
谢谢马丁。这实际上是我对这个问题的最后解决方案,也是一位同事计划自己使用的解决方案。与设备访问的实际成本相比,间接性并不是真正繁重或不利于性能。我只是想看看在沿着 LoadLibrary() 和 GetProcAddress() 的路线之前是否有另一种方式(更类似于 ELF 方式)。
只需重命名另一个兼容的 .DLL 即可。
不使用LoadLibrary()
,PE文件可以直接从不同的DLL文件中导入同名符号。这与 ELF 文件不同,ELF 文件不允许两个 DLL 包含同名的函数。
【参考方案1】:
是的。
我不怎么使用 Visual Studio,但是如果你使用 MSYS2 并安装一些 MinGW 包并更新它们,这种情况就会一直发生。
这就是我的意思:MSYS2 是一个用于 Windows 的开源软件发行版,除其他外,它提供了一堆本地 Windows 软件包。包管理器 (pacman
) 让您选择要在系统中包含哪些包,它会下载由 MSYS2 开发人员创建的 DLL 和 EXE。当 MSYS2 开发人员更新库时,您可以下载更新后的库包,使用该库的所有其他包将自动开始使用新的 DLL。通常这没有问题,因为新的库版本将与旧的库版本 ABI 兼容。
您确实不需要需要使用LoadLibrary
否则会弄乱您的源代码;链接器和操作系统应该能够为您解决这个问题。
示例
这是我与 MSYS2 一起抛出的一个最小示例,展示了它是如何工作的。
文件foo_legacy.c
代表您的旧版DLL。我添加了一些额外的符号,所以它不会与现代 DLL 太相似。
__declspec(dllexport) int eoo()
return 0;
__declspec(dllexport) const char * foo_name()
return "legacy";
__declspec(dllexport) int foo_version()
return 1;
__declspec(dllexport) int goo()
return 0;
文件foo_modern.c
代表现代实现:
__declspec(dllexport) const char * foo_name(void);
__declspec(dllexport) int foo_version(void);
int foo_version()
return 2;
const char * foo_name()
return "modern";
文件main.c
表示使用foo
API 的应用程序:
#include <stdio.h>
__declspec(dllimport) const char * foo_name(void);
__declspec(dllimport) int foo_version(void);
int main()
printf("%s %d\n", foo_name(), foo_version());
我的 build.sh 文件是一个构建和测试所有内容的 Bash 脚本:
#!/usr/bin/bash
set -uex
gcc -Wall foo_legacy.c -shared -o foo_legacy.dll
gcc -Wall foo_modern.c -shared -o foo_modern.dll
gcc -Wall -c main.c -I. -o main.o
gcc main.o foo_legacy.dll -o main.exe
./main.exe # output: "legacy 1"
mv foo_modern.dll foo_legacy.dll
./main.exe # output: "modern 2"
rm foo_legacy.dll
./main.exe # fails because foo_legacy.dll is not found
构建脚本运行 main.exe
三个不同的时间,表明它可以使用旧版 DLL,也可以使用现代 DLL,或者失败,具体取决于 foo_legacy.dll
中安装的内容。
【讨论】:
感谢 David Grayson,感谢大家抽出宝贵时间阅读和回复。这看起来很像我自己的代码,所以我很高兴得知它确实应该是可能的。可能是我的错误 0x22 完全不同,我将不得不做更多的调查来弄清楚那个特定的症状是什么。以上是关于您可以拥有多个具有通用导出/序数的 DLL 并在运行时进行交换吗?的主要内容,如果未能解决你的问题,请参考以下文章
无法定位序数40447于动态链接库libeay32.dll是啥状况
光晕2问啥我登不进一直出现:光晕2 无法定位序数2于动态链接库ADVAPI32.dll上