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”构造 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”,它以 aA*作为输入,并尝试将其转换为 a B*,使用dynamic_cast

void process(A* a)
{
  B* b = dynamic_cast<B*>(a);
  if(b)
    b->whatever_B();
  else
    printf("Failed!\n");
}

最后,我有主应用程序,A*在库之间传递 's:

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

问题

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

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

附加信息

  • 使用 gcc 4.4.1、libc6 2.10.1 (Ubuntu 9.10) 测试
  • 可用的示例代码
4

4 回答 4

15

我在这里找到了我的问题的答案。据我了解,我需要将“testc”中的类型信息提供给“testd”库。使用时要做到这一点dlopen(),需要做两件事:

  • 链接库时,将-E选项传递给链接器,以确保它将所有符号导出到可执行文件,而不仅仅是其中未解析的符号(因为没有)
  • 使用 加载库时dlopen(),添加RTLD_GLOBAL选项,以确保导出的符号testc也可用于testd
于 2010-02-28T18:58:01.810 回答
5

一般来说,gcc 不支持跨 dlopen 边界的 RTTI。我对这种混乱的 try/catch 有个人经验,但你的问题看起来更相似。可悲的是,恐怕您需要在 dlopen 中坚持使用简单的东西。

于 2010-02-28T17:20:26.747 回答
3

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

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

于 2010-03-10T15:56:10.013 回答
2

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

  1. 主应用程序链接到主机所以。
  2. 主应用程序使用 dlopen 加载主机(没有 RTLD_GLOBAL)。

在这两种情况下,都必须遵循gcc visibility wiki所述的说明。

如果使插件和主机的符号彼此可见(通过使用#pragma GCC visibility push/pop 或相应的属性)并使用 RTLD_GLOBAL 加载插件(从主机如此) 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));
}
于 2011-04-20T09:47:26.607 回答