与dlopen / dlsym一起使用时,dynamic_cast失败

时间:2010-02-28 17:05:58

标签: c++ linux dynamic-cast dlopen dlsym

简介

让我为这个长期问题道歉。它尽可能短,但不幸的是,它不是很短。

设置

我定义了两个接口,A和B:

class A // An interface
{
public:
  virtual ~A() {}

  virtual void whatever_A()=0;
};

class B // Another interface
{
public:
  virtual ~B() {}

  virtual void whatever_B()=0;
};

然后,我有一个共享库“testc”构建类C的对象,实现A和B,然后传递指向它们的A接口的指针:

class C: public A, public B
{
public:
  C();
  ~C();

  virtual void whatever_A();
  virtual void whatever_B();
};

A* create()
{
  return new C();
}

最后,我有第二个共享库“testd”,它以A*为输入,并尝试使用B*

将其强制转换为dynamic_cast
void process(A* a)
{
  B* b = dynamic_cast<B*>(a);
  if(b)
    b->whatever_B();
  else
    printf("Failed!\n");
}

最后,我有主要的应用程序,在库之间传递A*

A* a = create();
process(a);

问题

如果我构建我的主应用程序,链接到'testc'和'testd'库,一切都按预期工作。但是,如果我修改主应用程序以不链接“testc”和“testd”,而是使用dlopen / dlsym在运行时加载它们,则dynamic_cast将失败。 / p>

我不明白为什么。有线索吗?

其他信息

  • 使用gcc 4.4.1,libc6 2.10.1(Ubuntu 9.10)
  • 进行测试
  • Example code可用

4 个答案:

答案 0 :(得分:15)

我找到了问题here的答案。据我了解,我需要让'testc'中的typeinfo可用于库'testd'。要在使用dlopen()时执行此操作,需要完成两件事:

  • 链接库时,传递链接器-E选项,以确保它将所有符号导出到可执行文件,而不仅仅是那些未解析的符号(因为没有)
  • 使用dlopen()加载库时,添加RTLD_GLOBAL选项,以确保testc导出的符号也可用于testd

答案 1 :(得分:5)

通常,gcc不支持跨dlopen边界的RTTI。我有这个搞乱的尝试/捕获的个人经验,但你的问题看起来更像是相同的。可悲的是,我担心你需要坚持使用dlopen中的简单内容。

答案 2 :(得分:3)

我必须添加这个问题,因为我也遇到了这个问题。

即使提供-Wl,-E并使用RTLD_GLOBAL,dynamic_cast仍然会失败。但是,在实际应用程序的链接中传递-Wl,-E,而不仅仅是在库中,似乎已经修复了它。

答案 3 :(得分:1)

如果无法控制主应用程序的源,则-Wl,-E不适用。将-Wl,-E传递给链接器,同时构建自己的二进制文件(主机和插件)也无济于事。 在我的情况下,唯一可行的解​​决方案是从主机的_init函数加载和卸载我的主机,因此使用RTLD_GLOBAL标志(参见下面的代码)。此解决方案适用于两种情况:

  1. 主应用程序链接到主机。
  2. 主应用程序加载主机,因此使用dlopen(不含RTLD_GLOBAL)。
  3. 在这两种情况下,都必须遵循gcc visibility wiki所述的说明。

    如果使插件和主机的符号彼此可见(通过使用#pragma GCC可见性推送/弹出或相应的属性)并使用RTLD_GLOBAL 1加载插件(来自主机)将工作也没有自己加载和卸载(如上面给出的链接所述)。 这个解决方案使得2.工作也不是以前的情况。

    
    // get the path to the module itself
    static std::string get_module_path() {
        Dl_info info;
        int res = dladdr( (void*)&get_module_path, &info);
        assert(res != 0); //failure...
    
        std::string module_path(info.dli_fname);
        assert(!module_path.empty()); // no name? should not happen!
        return module_path;
    }
    
    void __attribute__ ((constructor)) init_module() {
        std::string module = get_module_path();
    
        // here the magic happens :)
        // without this 2. fails
        dlclose(dlopen(module.c_str(), RTLD_LAZY | RTLD_GLOBAL));
    }