I'm in trouble with some dynamic_cast on objects created by shared libraries:
The architecture is something like:
class A;
class B : virtual public A; // one of the several interfaces
class C : public B; // defined only in the shared library
C object but it returns a
dynamic_cast<A*>(pointerToCclass) because the main application is
unaware of CB
from the returned A pointer it fails.I suspect that some difference in the vtables created in the main and the shared code could be the reason. Anyway, initially I was unaware of this problem beacuse the main application
calls the method void * A::getInterface( int ifEnum ), so the downcast is performed successfully by the shared library code and returned as a void pointer. The main application then performs then a reinterpret_cast to bind the void pointer to the desired interface.
All worked till now, when a multi-inheritance schema (when C implements more than one interface) seems to be unstable and leads to Segmentation faults.
Are my suspects true? There's a better way, or a well known method, to implement a similar architecture?
Thank you
I attach some semplified code of my real application with the essential actors. First the common definitions:
class A
{
public:
typedef A* (*getAobject_fn)(void);
static A * Load( char * filename ) {
void * objhlib = dlopen( filename, RTLD_NOW );
getAobject_fn fp = (getAobject_fn) dlsym( objhlib, "getAobject" );
return fp();
}
virtual A * Create() = 0;
virtual void * getInterface( int ifEnum ) = 0;
};
class B1 : virtual public A {
public:
// some inline or pure virtual functions here
};
class B2 : virtual public A {
public:
// some other inline or pure virtual functions here
};
The header of the shared library (actually the C class is not visible to the main application because the .so is loaded at runtime and .h not included in the main):
class C : public B1, public B2
{
public:
A * Create() { return new C; }
void * getInterface( int ifEnum ) {
if( ifEnum==INTERFACE_ID_B1 )
return dynamic_cast<B1*>(this);
if( ifEnum==INTERFACE_ID_B2 )
return dynamic_cast<B2*>(this);
return 0;
}
};
extern "C" { A * getAobject(); } // probably useless
In the body of the shared library:
C obj;
A * getAobject() { return dynamic_cast<A*>(&obj); } // equivalent to return &obj;
And finally, in the main application:
// In the Init procedure
A * p = A::Load( "foo.so" );
A * pBobj = p->Create(); // pBobj is kept for the entire lifetime
B1 * b1 = reinterpret_cast<B1*>(pBobj->getInterface( INTERFACE_ID_B1 ));
B2 * b2 = reinterpret_cast<B2*>(pBobj->getInterface( INTERFACE_ID_B2 ));
// NOTE:
// b1 = dynamic_cast<B1*>(pBobj) and
// b2 = dynamic_cast<B2*>(pBobj)
// will fail
How is the shared object loaded? g++ uses the address of the
RTTI information to resolve dynamic_cast. Traditionally, by
default in Unix, the first symbol to be loaded will be used by
all of the shared objects, so there will be no problem. This
depends on the mode used in dlopen, however; if RTLD_LOCAL
is specified, the symbols in that shared object (and in shared
objects which are implicitly loaded as a result of loading that
shared object) won't be visible outside the shared object.
(I've had this problem with Java plug-ins. Java loads the
shared object with RTLD_LOCAL, and dynamic_cast won't work
accross shared objects loaded implicitly by the shared object
Java loads.)
With regards to the main executable: most Unix linkers will make
the symbols available, as if the executable had been loaded
using RTLD_GLOBAL. Most, but not all; the GNU linker, for
example, does not do this, and symbols in the main executable
are not available for shared objects. If you need them to be
available, you must use the -rdynamic option when building the
executable (which translates to the -export-dynamic option to
the linker). Alternatively, if you need to break your
application down into separate shared objects, you might
consider putting practically everything in shared objects, and
making the main executable nothing but a simple library loader,
calling dlopen on all of the shared objects, and then dlsym
to get the address of the actual function you want to execute,
and call it through the address. (This is basically how we
solved the problem with the Java plug-ins. All Java loaded was
our loader module, which then did the dlopen. By doing them
explicitly, we could control the options to dlopen.)
EDIT:
On rereading your question, I'm not sure this is the right
answer. You're passing through a void*, which means that you
have no access to the RTTI (or even to vtables). The rules
concerning void* (C++ rules, this time, not g++) are clear:
the only thing you can do with a void* is convert it back to
the original pointer type. In particular, the sequence
Derived* → void* → Base* is undefined behavior.
It will typically work if only single inheritance is involved
(but even then it is still undefined behavior), but not
otherwise. So if the shared object converts an A* to
a void*, and the void* is later converted to a B*, you
have undefined behavior, and shouldn't expect it to work.
Converting the void* to an A* first, and then converting it
to a B*, should work. Even better, however: declare the
function you're calling to return an A*, rather than
a void*. In general, you should avoid void* as much as
possible, especially in C++.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With