您可以拥有多个具有通用导出/序数的 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 文件并确保导出的符号在数量、名称和序数上匹配。仍然没有 - 发生同样的错误。

对此我显然还没有弄清楚,希望得到一些指导。这真的可能吗?我从哪里开始寻找(哪些工具)以确定下一步要采取的步骤。

【问题讨论】:

你为什么不编写 DLL blah.dll 来使用LoadLibrary() 加载两个 DLL?您将使用GetProcAddress() 在两个DLL 中获得指向blah_open() 的指针,并将指针存储在blah_open_legacyblah_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 并在运行时进行交换吗?的主要内容,如果未能解决你的问题,请参考以下文章

编写兼容的DLL替换按顺序和名称导出两者

无法定位序数4046于动态链接库libeay32.dll上

无法定位序数40447于动态链接库libeay32.dll是啥状况

光晕2问啥我登不进一直出现:光晕2 无法定位序数2于动态链接库ADVAPI32.dll上

Glassfish 管理控制台可以拥有多个具有特定权限的用户吗?

VS2017里使用mysql出现错误,无法定位序数3283...