【问题标题】:C++: implementation of a class methods in a separated shared libraryC++:在分离的共享库中实现类方法
【发布时间】:2012-02-07 01:30:21
【问题描述】:

我发现我可以在共享库中实现类的某些部分,只要符号在使用时被加载。

myclass.h
---

class C {
void method();
}

main.cpp
---

#include "myclass.h"
int main() {
    //dynamically load mylib.so using dlopen/dlsym/dlclose
        ...
    C *c = new C();
    c->method();
    delete c;
}

mylib.so compiled separately:
====
mylib.cpp
---

#include "mylib.h"
void C::method() {
...
}

这很好用。

但是,一旦我用完 C::method(),我想卸载它,这样我就可以更改、重新编译和重新加载它,而无需重新启动主程序

int main() {
    //dynamically load mylib.so using dlopen/dlsym/dlclose
        ...
    C *c = new C();
    c->method();
    delete c;
    // close the lib, remove the handle...
         ....
    char pause;
    cin << pause; // before carrying on change the C::method, recompile it

    //dynamically load *Again* mylib.so using dlopen/dlsym/dlclose
        ...
    C *c = new C();
    c->method();
    delete c; 
}

问题是在第一次dlclose时它没有卸载库,可能是因为我的类C的一个实例存在。 有什么办法可以强制这样做?

(在 Linux Ubuntu 10.04 上使用 g++ 4.2.3)

【问题讨论】:

  • 我认为没有跟踪到你有一个类的实例(尤其是通过指针)。你怎么知道,库没有卸载?
  • 你能在dlclose 之后打电话给dlerror 看看为什么库没有被卸载吗?
  • @Krizz:尽管我做了一些改变,但行为保持不变。例如。 cout
  • @parapura rajkumar:在“cout
  • 你是用RTLD_GLOBAL打开图书馆吗?这可能会导致其他库在其中使用符号,从而阻止它在关闭时卸载。如果您只想查找带有 dlsym 的符号,则不需要该标志。

标签: c++ class implementation dlopen


【解决方案1】:

简答

它不会按照你的方式工作。您的方法可能还存在其他尚未解决的问题。

为什么它不起作用

您的程序/库中的未定义符号在不同时间被解析。在大多数系统上,数据引用(全局变量、类 vtable 等)总是在加载程序/库时解析。除非设置了一些特殊选项(dlopen 或 LD_BIND_NOW 环境变量的 RTLD_NOW 参数),否则代码引用在首次在某些系统上使用时会被解析(“惰性查找”;它至少发生在 Linux 和 Mac OS X 上)。解决这些问题后,将不会进行新的查找。

如果你dlopen你的库在惰性查找方法完成之前带有 RTLD_GLOBAL 标志,则将使用你库中的方法。现在解决了对该方法的代码引用;它不会再改变了。您的主程序现在正式使用您的 dlopened 库中的符号,因此 dlclose 将不再关闭该库 - dlclose 只会删除您对它的显式句柄。

简而言之,您应该只期望卸载仅通过显式调用 dlsym 使用的库。

该怎么做

您可以做的是让您的库提供派生类实现。您可以将您的类 C 定义为抽象基类:

class C
{
public:
    virtual void method();
};

在您单独编译的库中,您将定义一个派生类和一个创建该派生类对象的函数:

class D : public C
{
public:
    virtual void method();
};

void D::method()
{
    // ...
}

extern "C" C* createC()
{
    return new D();
}

现在,在您的主程序中,您将使用 dlopen 加载库,使用 dlsym 获取指向 createD 的函数指针并调用它以获取对象。当所有对象都消失后,您可以调用dlclose,重新编译您的库,然后再次执行整个操作:

typedef C* (*creatorFunction)();

int main()
{
    for(;;)
    {
        void *handle = dlopen("mylib.so", 0);
        creatorFunction create = (creatorFunction) dlsym(handle, "createC");

        C *c = (*create)();
        c->method();
        delete c;

        dlclose(handle);

        char pause;
        cin << pause;
    }
    return 0;
}

【讨论】:

  • 为什么不一样? createC() 函数可以在库加载之间移动,对吧?
  • @AndresJaanTack 是的,但更重要的是:在原始代码中,c->method() 行是对库的直接函数调用,这会导致惰性符号查找。一旦发生惰性符号查找,C::method() 总是引用该代码,并且无法再卸载该库。在这里,没有直接调用; (*create)() 通过从 dlsym 获得的函数指针,而 c->method() 是一个虚函数调用,所以dlclose 仍然会卸载库。
  • @wolfgang:很好的解释。这看起来更好的设计,不将对象的代码拆分到单独的文件中。但是,在某些测试用例中它会很方便,并且因为“一旦解决了对该方法的代码引用,它就不会再次更改”,因此它是“唯一的”。我不打算对其进行测试,但如果有办法撤销它,那将使声明和实现的这种分离成为可能。
【解决方案2】:

感谢大家的帮助,只是一个通知:

不要忘记在 C 类中将方法初始化为零(该类不在共享库中),并将虚拟销毁器设置为默认值

如果你想知道 dlopen 的标志是 0 --> 它是 RTLD_LOCAL

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-21
  • 2011-04-22
  • 2011-08-14
相关资源
最近更新 更多