在我的项目中,第三方依赖于静态库(从现在开始称为libsomething
)。最近,libsomething
已经出现在另一个版本中。我的任务是为我的软件提供旧版和新版的支持。在任何给定时间,只有一个版本的libsomething
在运行时使用,但是哪个版本应该可以在程序运行之间进行配置。
我在WinXP上使用MSVC2005,第二个目标是准备切换到Linux和GCC。
由于libsomething
的两个版本都使用相同的符号,因此将它们连接到我的可执行文件是不可能的,因为两个版本的符号将在链接时发生冲突。
虽然我可以创建两个可执行文件(一个链接旧版本,另一个使用新版本),但我无法决定在最终部署环境中调用哪个可执行文件(遗留原因)。
我提出了为每个版本的libsomething
创建动态库包装的想法,并根据某些配置文件在运行时链接它们。使用MSCV,这将意味着沿着使用LoadLibrary()
,GetProcAddress()
等的道路,而在Linux上,我将不得不使用dlopen()
和dlsym()
。
据我所知,使用libtool
(即libtldl
)包含此平台依赖项以使用共享库。这是一条合适的道路吗?是否有更好的(或至少是不同的)方式? libtldl
的替代品是否作为开源存在?
已经有几年了,但我想提一下完整性的另一种解决方案。您可以为所有必需的函数生成简单的存根,而不是手动dlopen
和dlsym
,并在第一次调用(或程序启动时)决定需要哪个库版本,加载它并解析地址。
您可以编写专门为您的项目定制的脚本或使用Implib.so工具:
# This will generate mylib.so.init.c and mylib.so.tramp.S
# which implement stubs. These need to be linked to your
# executable.
$ implib-gen.py mylib.so
Implib.so仅适用于Linux,但应该很容易适应Windows。
我知道你说你不能使用两个可执行文件,因为决定执行哪个,但你不能在可执行文件之间来回exec
,具体取决于在配置中选择哪个版本?
在Linux上,您可以更轻松地链接到共享库并使用符号链接来更正版本 - IMO比使用dlopen()
+ dlsym()
更容易。
因此,您将为库的旧版本和新版本创建共享库:
g++ -shared -o libshared.so.1.1 -Wl,-soname=libshared.so.1 -L. -Wl,--whole-archive libstatic.a.old -Wl,-no-whole-archive
和
g++ -shared -o libshared.so.1.2 -Wl,-soname=libshared.so.1 -L. -Wl,--whole-archive libstatic.a.new -Wl,-no-whole-archive
创建符号链接:
ln -s libshared.so.1.1 libshared.so.1
ln -s libshared.so.1 libshared.so
构建应用程序,将其链接到旧版本的库。我想两个版本都是二进制兼容的(ABI没有坏掉),但新版本可能有一些新的符号。
g++ -o myapp myapp.cpp -L. -lshared
由于共享库的SONAME
是libshared.so.1
,您的应用程序将依赖于它并将在libshared.so.1
或/etc/ld.so.conf
的路径中搜索LD_LIBRARY_PATH
在运行应用程序之前,您可以将libshared.so.1
符号链接设置为指向libshared.so.1.2
或libshared.so.1.1
。
--whole归档 对于--whole-archive选项后命令行中提到的每个归档,请在链接中包含归档中的每个目标文件,而不是在归档中搜索所需的目标文件。这通常用于将存档文件转换为共享库,从而强制将每个对象包含在生成的共享库中。此选项可能不止一次使用。 从gcc使用此选项时的两个注意事项:首先,gcc不知道此选项,因此您必须使用-Wl,-whole-archive。其次,不要忘记在归档列表之后使用-Wl,-no-whole-archive,因为gcc会将自己的归档列表添加到您的链接中,您可能也不希望此标记也影响这些归档。
-soname =名称 创建ELF共享对象时,将内部DT_SONAME字段设置为指定的名称。当可执行文件与具有DT_SONAME字段的共享对象链接时,则在运行可执行文件时,动态链接器将尝试加载由DT_SONAME字段指定的共享对象,而不是使用为链接器指定的文件名。