Вопрос

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
  1. The main application load the shared library and requests a new object
  2. The shared library creates a new C object but it returns a dynamic_cast<A*>(pointerToCclass) because the main application is unaware of C
  3. When the main application tries to downcast to B 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++.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top