让我为这个长期问题道歉。它尽可能短,但不幸的是,它不是很短。
我定义了两个接口,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>
我不明白为什么。有线索吗?
答案 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标志(参见下面的代码)。此解决方案适用于两种情况:
在这两种情况下,都必须遵循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));
}