根据用户配置在运行时链接共享对象
Linking shared objects at runtime depending on user configuration
TL;博士
我有一个想在我的程序中使用的库,它有两个不同的版本。两个版本提供相同的接口,但用于编译它们的选项不同。
但是,我现在想使用特定版本的库,由于两个版本都适用于不同的任务,并且用户应定义要执行的任务,我需要决定在运行时使用哪个库。
我知道我可以使用dlopen
和dlsym
根据用户的选择在运行时加载正确的库,但是界面非常大,将我需要的所有内容加载到不同的函数指针中会非常乏味......
问题所在
我有一个库,它有两个不同的版本。两个版本都提供相同的界面,但适合的任务有所不同。 文件树如下所示:
lib
- lib_task1
- libsharedobj.so
- lib_task2
- libsharedobj.so
我想为用户提供选择在运行时执行的任务的可能性。因此,我还需要决定在运行时选择哪个库。 我的想法是编写一个包装器,它提供与库相同的接口,我将在其中dlopen
所需的库并将相应的符号dlsym
到函数指针中。但是,库接口非常大,按照描述包装它会非常乏味,而且它是一个 C 接口,所以它还包含很多我希望不要在包装器外部看到的原始指针。
这里有一个小例子
// library interface
typedef struct {
// ...
} a_type;
void do_something(a_type* param);
// wrapper
class LibWrapper {
private:
void (*do_something)(a_type*);
void* lib;
public:
LibWrapper(const bool task_one) { // specified by the user
if (task_one) {
lib = dlopen("/usr/lib/lib_task1/libsharedobj.so", RTLD_NOW);
} else {
lib = dlopen("/usr/lib/lib_task2/libsharedobj.so", RTLD_NOW);
}
do_something = dlsym(lib, "do_something");
}
~LibWrapper() {
if (lib) {
dlclose(lib);
}
}
void do_something(std::unique_ptr<a_type> param) {
do_something(param.get());
}
};
问题
有没有更好的方法,或者我真的需要逐个加载每个符号?
操作系统:ubuntu 14.04。
兼容性: C++11
混淆LD_PRELOAD设置的一种方法是,当您的程序启动时,如果LD_PRELOAD设置不当,请确定您想要的设置,添加它,然后立即使用您自己的参数执行以重新启动! 我认为LD_PRELOAD在这里更有意义,如果它能满足您的需求,因为它更容易检测它是否按照您需要的方式设置。
您可以通过在启动程序之前设置环境变量LD_LIBRARY_PATH
来解决此问题。 如有必要,编写一个启动程序来执行此操作:
LD_LIBRARY_PATH=lib/lib_task1 ./myprog # or lib_task2
然后ld.so
将首先在指定的目录中查找libsharedobj.so
,无论链接了哪个目录(即ldd myprog
显示哪个目录)。
你可以考虑使用 objcopy 来重命名库中的公共符号吗?
[更多详情请见要求]
如果将两个库中的所有竞争函数重命名为非竞争命名空间,则可以同时加载它们,并在运行时选择所需的符号。
如果可以使唯一名称看起来像真正的 c++ 命名空间,则应该能够通过在命名空间定义中 #including 现有头文件来重用它。
下面是一些全局和命名空间损坏的名称:
U _Z9MyTestFn1Pv
U _Z9MyTestFn2Pv
U _ZN2N19MyTestFn1EPv
U _ZN2N19MyTestFn2EPv
U _ZN2N29MyTestFn1EPv
U _ZN2N29MyTestFn2EPv
你可以在这里解散它们:https://demangler.com/
使用objcopy --redefine-sym old=new
或objcopy --redefine-syms=filename
进行重命名。重命名可以使用 nm 和 sed 生成。
通过一些额外的聪明的宏工作,你甚至可以让它编写一个c风格的函数表原型。不过,您仍然需要填充该表,但是通过拥有真正的原型,您不太可能遭受使用dlsym太容易出现的胖手指错误。
标头读取以下内容以允许指针声明:
int (PTR_MAYBE FirstExternalFn) ( int firstArg, ... ) ;
std::stringint (PTR_MAYBE SecondExternalFn) ( bool firstArg, ... ) ;
或者这可能太多了,但也允许自动填充跳转表:
RETURNS(int) (PTR_MAYBE FirstExternalFn) ARGUMENTS ( int firstArg, ... ) ENDLINE
RETURNS(std::stringint) (PTR_MAYBE SecondExternalFn) ARGUMENTS ( bool firstArg, ... ) ENDLINE
第三种方法旨在帮助填充填充虚拟 c++ 类,它要求您将每个参数装饰为表单OneWordType (name)
,这在模板中相对容易,即const char* name
变得DecorateArg<char>::const_ptr (name)
.然后,这允许您调用声明!当然,使用虚拟类的代价是代码带有一个你永远不会使用的 this 指针,并且你正在进行额外的填充程序调用。
注意:此文件被多次包含,并且没有包含保护,但它不再真正用于直接使用。您可以 #error 是否未定义父包含。
您可以通过多种方式使用它:
//Declare the external methods
#define PTR_MAYBE
#define ARGUMENTS
#define ENDLINE ;
namespace FirstLib {
#include Header.hpp
}
// And the second library
namespace SecondLib {
#include Header.hpp
}
//define the indirection table declaration
#define PTR_MAYBE *
struct Indirection
{
#include Header.hpp
void* dummy;
};
namespace FirstLib
{
extern Indirection indirection;
}
namespace SecondLib
{
extern Indirection indirection;
}
// populate the table
#define ARGUMENTS(...)
#define ENDLINE ,
namespace FirstLib
{
Indirection indirection =
{
#include Header.hpp
nullptr
};
}
/* looks like:
FirstExternalFn,
SecondExternalFn,
nullptr
*/
namespace SecondLib
{
Indirection indirection =
{
#include Header.hpp
nullptr
};
}
这有点令人讨厌的宏黑客,但如果你愿意自己构建表,你不需要 ARGUMENTS 或 ENDLINE,这让它更加理智。
我们现在可以访问 2 个间接寻址表,FirstLib::indirection,SecondLib::indirection,您可以在其中或多或少地直接从主代码调用库方法,方法是将指针分配给您喜欢的:
std::cout << currentIndirection->FirstExternalFn(nullptr);
这在精神上与在 C 中使用 dlopen 时是否有一种优雅的方法来避免 dlsym?基本上,您正在寻找Windows导入库的等效项,该库将提供存根符号来满足静态链接器,然后在运行时dlopen
具有实际实现的动态库。
开箱即用的 Linux 不支持导入库,因此人们通常手动或通过为特定项目量身定制的脚本(例如 GLEW)实现存根。我最近开发了 Implib.so 来自动生成与POSIX兼容的导入库。模错误,您应该能够将其用作
$ implib-gen.py --dlopen-callback=mycallback
(mycallback
选择库的适当版本并dlopen
)。
- 链接共享库依赖项未在 ldd 中列出
- Gradle cpp 插件不链接共享库
- 从大型 github 项目链接共享库
- 如何在工具ROS中链接共享库
- Linux C++.链接共享对象和主对象
- 共享对象与提升program_options静态链接;应用程序链接共享
- G++ 5.5 不链接共享库
- 根据用户配置在运行时链接共享对象
- 在静态库中链接失败,但链接共享库成功
- G 无法链接共享对象的静态版本
- 仅当标头在 CMake 中更改时,才重新链接共享库
- 动态链接共享库中的全局变量和静态变量会发生什么情况
- CMake:从外部项目链接共享C++对象会生成具有相对路径的二进制文件,而不是绝对路径
- 在VirtualBox的共享文件夹中构建符号链接共享库
- 编译对象后链接共享库时出错
- 如何通过GNU Autotools链接共享库
- 如何以正确的方式编译和链接C/ c++共享对象库
- 无法链接共享对象
- 在 R 包中生成和链接共享库 - 代码编译、链接,但不会加载
- 如何在cmake中链接共享对象