C++在Linux平台运行时加载共享库并提取类实现

Posted

技术标签:

【中文标题】C++在Linux平台运行时加载共享库并提取类实现【英文标题】:C++ load shared library and extract class implementations at runtime on linux platform 【发布时间】:2014-04-01 19:24:22 【问题描述】:

在 C++ 中,是否可以在执行时加载共享库?

我希望用户在运行时选择要加载的共享库,而不需要重新编译整个程序。

dlopen()是C的解决方案,但我的程序是用C++/Qt编写的,提取的符号是Qt风格的类,有没有更“c++”的方式来做到这一点。

【问题讨论】:

这是什么平台? 是的,这是可能的,但与c++ 或qt 没有本质上的关系,它取决于操作系统如何在运行时将共享库加载到您的程序中。从您的代码中运行它有什么特别的问题? 我想从lib中提取的是qt类,我不知道在qt中有没有这样的机制。 【参考方案1】:

您可以在 Qt 中使用QLibrary 以两种方式完成此操作。以下示例在运行时以两种不同的方式从共享库调用函数:

#include <QLibrary>
#include <QDebug>

class  Dynamic_library

public:
    Dynamic_library();
    virtual int sum( int len, int * data );
;

typedef Dynamic_library * (*get_object_func)();
typedef int (*call_sum_func)(int len , int * data);

int main(int argc, char *argv[])

    QCoreApplication a(argc, argv);

    QLibrary library( "./dynamic_library" );
    library.load();
    if( !library.isLoaded() )
    
        qDebug() << "Cannot load library.";
        return 0;
    
    call_sum_func call_sum = (call_sum_func)library.resolve( "call_sum" );
    if( call_sum )
    
        //Dynamic_library * obj=get_object();

        int * a=new int[3];
        a[0]=2;
        a[1]=3;
        a[2]=4;
        qDebug() << "sum of 2+3+4' = " << call_sum( 3, a ) <<"\n";

        delete [] a;
    

    get_object_func get_object = (get_object_func)library.resolve( "get_object" );
    if( get_object )
    
        Dynamic_library * obj=get_object();

        int * a=new int[3];
        a[0]=7;
        a[1]=8;
        a[2]=9;
        qDebug() << "sum of 7+8+9' = " << obj->sum(3, a );

        delete [] a;
    

    return a.exec();

共享库的代码如下:

class DYNAMIC_LIBRARYSHARED_EXPORT Dynamic_library

public:
    Dynamic_library();
    virtual int sum( int len, int * data );
;

extern "C" Q_DECL_EXPORT Dynamic_library * get_object()

     return new Dynamic_library();


extern "C" Q_DECL_EXPORT int call_sum(int len, int * data)

     return Dynamic_library().sum(len,data);



Dynamic_library::Dynamic_library()




int Dynamic_library::sum( int len, int *data )

    int sum = 0;
    for(int i=0; i<len; ++i )
        sum += data[i];

    return sum;

【讨论】:

我已将共享库的代码添加到答案中。如您所见,外部“C”使您可以动态访问 C++ 类。 “sum”的成员函数的“虚”声明是必须的吗? 必须是虚拟的。因为我们声明了它,在使用的时候没有定义它。 非常感谢,我也发现无法从类中检索静态成员【参考方案2】:

如果目标库本身,或者至少它的规范在您的控制之下,那么您不应该使用QLibrary - 而是使用Qt plugin system。它不需要其他需要的通过指针调用体操。

如果你坚持使用类似dlopen 的机制,那么QLibrary 就没有C 特有的东西。明显的限制是,您尝试打开的库必须使用与用于编译您自己的代码的 ABI 兼容的 C++ 编译器进行编译。在 Windows 上,这实际上意味着使用相同的 MSVC 版本。

除此之外,您还必须look up the mangled version of the symbol。完成此操作后,您可以使用与其匹配的函数/方法指针调用该符号。这个won't work on constructors/destructors,设计使然。如果您希望创建新的对象实例,则需要库提供的静态工厂方法。

如果库不提供工厂方法,您可以实现一个 shim 库,该库通过通用名称链接到目标库并提供工厂方法。您仍然需要通过函数/方法指针调用各个方法。

    创建一个临时文件夹。 将 shim 库复制到临时文件夹。 将重命名为通用名称的目标库复制到临时文件夹中。 保存LD_LIBRARY_PATH环境变量的值。 将临时文件夹添加到LD_LIBRARY_PATH。 打开/加载库。 恢复LD_LIBRARY_PATH 的保存值。

当然,对于库公开的任何接口,您都必须拥有头文件。通常,仅给定一个动态库文件就无法对其进行重构-主要是因为损坏的符号没有所用类型的完整结构信息。例如,即使您可以找到给定类的构造函数,您也不知道该类实例有多大(它的sizeof)。

【讨论】:

【参考方案3】:

是的,可以在大多数操作系统上执行您所描述的操作,但是您如何执行取决于系统,并且无论系统如何,您都需要做更多的工作才能实现它。

一般步骤是:

    加载库 对于库中您感兴趣的每个符号,找到它并将其存储到一个变量中以供以后使用。 (这可以根据需要完成,而不是立即完成。)

例如,在 *nix 类型系统上的伪代码(阅读:这不会编译!)中,假设您的共享库中有这个:

// I'm declaring this _extern "C"_ to avoid name mangling, making it easier to
// specify a symbol name for dlsym() later
extern "C" int myFunction() 
    return 10;

假设它位于名为 libmyFunction.so 的库中。例如,您的主应用程序可以:


    void *handle = dlopen("libmyFunction.so", <flags>);
    if (!handle) return; // error: cannot locate the library!

    int (*func)() = (int (*)())dlsym(handle, "myFunction");
    if (!func) return; // error: cannot locate the symbol!

    printf("The function returns: %d\n", func());

如果您需要在 Windows 上执行此操作,则概念相同,但函数调用不同。

【讨论】:

以上是关于C++在Linux平台运行时加载共享库并提取类实现的主要内容,如果未能解决你的问题,请参考以下文章

C++ 中的崩溃恢复

Java方法区

jvm 方法区

如何在 GCC Linux 中指定非默认共享库路径?运行时出现“加载共享库时出错”

具有平台 API 的 C++ 全局热键

C#Reflection - 如何在运行时重新加载类?