dynamic_cast在使用dlopen/dlsym时失败

13

简介

首先我要为这个问题的长度道歉,尽管我已经尽可能地简化了它,但它仍然很长。

设置

我定义了两个接口,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”的共享库,用于构造实现A和B接口的C类对象,并传递它们的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*作为输入,并尝试使用dynamic_cast将其转换为B*

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将失败。

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

附加信息

  • 使用gcc 4.4.1,libc6 2.10.1(Ubuntu 9.10)进行测试
  • 示例代码可用

这是相当特定于操作系统的。但我想问题在于主模块无法访问所需的RTTI数据,以使dynamic_cast正常工作。 - Hans Passant
4个回答

15

我在这里找到了我的问题的答案。据我所知,我需要使'testc'中的typeinfo对库'testd'可用。当使用dlopen()时,需要执行以下两个额外步骤:

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

它确实如此。如果您想亲自查看,请按照问题中的“示例代码”链接并进行上述修改。 - Kees-Jan

5

通常情况下,gcc不支持在dlopen边界跨越RTTI。我个人有相关的经验,当使用try/catch时会出现问题,但你的问题看起来更多是相同的情况。遗憾的是,我担心你需要在dlopen之间坚持使用简单的东西。


1
我没有尝试过,但我猜按照http://gcc.gnu.org/faq.html#dso上的说明也可以解决你的问题。 - Kees-Jan

3

我必须补充一下这个问题,因为我也遇到了这个问题。

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


我也不得不这样做。实际上在我的情况下,库不需要-Wl,-E,只有主应用程序需要。 - Nathan Monteleone

2
如果没有对主应用程序的源代码进行控制,则-Wl,-E无法使用。在构建自己的二进制文件(主机so和插件)时将-Wl,-E传递给链接器也没有帮助。在我的情况下,唯一可行的解决方案是在主机so本身的_init函数中使用RTLD_GLOBAL标志加载和卸载主机so(请参见以下代码)。这个解决方案在以下两种情况下都有效:
  1. 主应用程序链接到主机so。
  2. 主应用程序使用dlopen加载主机so(不使用RTLD_GLOBAL)。
在这两种情况下,都必须遵循gcc visibility wiki所述的说明。
如果使用#pragma GCC可见性push/pop或相应的属性使插件和主机so的符号彼此可见,并使用RTLD_GLOBAL从主机so加载插件,则第1步也可以正常工作,而不需要加载和卸载自己的so(如上面提供的链接所述)。此解决方案还可使第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));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接