dynamic_cast fails when used with dlopen/dlsym
Intro
Let me apologise upfront for the long question. It is as short as I could make it, which is, unfortunately, not very short.
Setup
I have defined two interfaces, A and 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;
};
Then, I have a shared library "testc" constructing objects of class C, implementing both A and B, and then passing out pointers to their A-interface:
class C: public A, public B
{
public:
C();
~C();
virtual void whatever_A();
virtual void whatever_B();
};
A* create()
{
return new C();
}
Finally, I have a second shared library "testd", which takes a A*
as input, and tries to cast it to a B*
, using dynamic_cast
void process(A* a)
{
B* b = dynamic_cast<B*>(a);
if(b)
b->whatever_B();
else
printf("Failed!\n");
}
Finally, I have main application, passing A*
's between the libraries:
A* a = create();
process(a);
Question
If I build my main application, linking against the 'testc' and 'testd' libraries, everything w开发者_开发知识库orks as expected. If, however, I modify the main application to not link against 'testc' and 'testd', but instead load them at runtime using dlopen
/dlsym
, then the dynamic_cast
fails.
I do not understand why. Any clues?
Additional information
- Tested with gcc 4.4.1, libc6 2.10.1 (Ubuntu 9.10)
- Example code available
I found the answer to my question here. As I understand it, I need to make the typeinfo available in 'testc' available to the library 'testd'. To do this when using dlopen()
, two extra things need to be done:
- When linking the library, pass the linker the
-E
option, to make sure it exports all symbols to the executable, not just the ones that are unresolved in it (because there are none) - When loading the library with
dlopen()
, add theRTLD_GLOBAL
option, to make sure symbols exported bytestc
are also available totestd
In general, gcc does not support RTTI across dlopen boundaries. I have personal experience with this messing up try/catch, but your problem looks like more of the same. Sadly, I'm afraid that you need to stick to simple stuff across dlopen.
I have to add to this question since I encountered this problem as well.
Even when providing -Wl,-E
and using RTLD_GLOBAL
, the dynamic_casts still failed. However, passing -Wl,-E
in the actual application's linkage as well and not only in the library seem to have fixed it.
If one have no control over the source of the main application, -Wl,-E is not applicable. Passing -Wl,-E to the linker while building own binaries (the host so and the plugins) do not help either. In my case the only working solution was to load and unload my host so from the _init function of the host so itself using RTLD_GLOBAL flag (See code below). This solution works in both cases:
- the main application links against the host so.
- the main application loads host so using dlopen (without RTLD_GLOBAL).
In both cases one has to follow the instructions stated by gcc visibility wiki.
If one makes the symbols of the plugin and the host so visible to each other (by using #pragma GCC visibility push/pop or corresponding attribute) and loads the plugins (from the host so) by using RTLD_GLOBAL 1. will work also without loading and unloading the own so (as mentioned by link given above). This solution makes 2. also work which has not been the case before.
// 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));
}
精彩评论