简体   繁体   English

共享库的Dynamic_cast麻烦

[英]Dynamic_cast troubles over shared libraries

I'm in trouble with some dynamic_cast on objects created by shared libraries: 我在共享库创建的对象上遇到一些dynamic_cast麻烦:

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 共享库创建了一个新的C对象,但它返回了dynamic_cast<A*>(pointerToCclass)因为主应用程序不知道C
  3. When the main application tries to downcast to B from the returned A pointer it fails. 当主应用程序尝试从返回的A指针向下转换到B ,它将失败。

I suspect that some difference in the vtables created in the main and the shared code could be the reason. 我怀疑在主代码和共享代码中创建的vtables有所不同可能是原因。 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. 无论如何,最初我没有意识到这个问题,因为主应用程序调用了方法void * A::getInterface( int ifEnum ) ,因此向下转换由共享库代码成功执行,并作为void指针返回。 The main application then performs then a reinterpret_cast to bind the void pointer to the desired interface. 然后,主应用程序执行reinterpret_cast ,将void指针绑定到所需的接口。

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. 到现在为止,所有工作都起作用了,当时一个多继承模式(当C实现多个接口时)似乎不稳定并导致分段错误。

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): 共享库的标头(实际上,C类对主应用程序不可见,因为.so是在运行时加载的,而.h不包含在主库中):

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 . g ++使用RTTI信息的地址来解析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. 传统上,在Unix中,默认情况下,所有共享库都将使用第一个要加载的符号,因此不会有问题。 This depends on the mode used in dlopen , however; 但是,这取决于dlopen使用的模式。 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. 如果RTLD_LOCAL ,则该共享库中的符号(以及由于加载该共享库而隐式加载的共享库中的符号)在共享库外不可见。 (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.) (我在使用Java插件时遇到了这个问题。Java用RTLD_LOCAL加载共享对象,而dynamic_cast不能在Java共享对象隐式加载的共享对象上工作。)

With regards to the main executable: most Unix linkers will make the symbols available, as if the executable had been loaded using RTLD_GLOBAL . 关于主要可执行文件:大多数Unix链接器都使符号可用,就好像该可执行文件是使用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. 例如,GNU链接器执行此操作,并且主可执行文件中的符号不​​可用于共享对象。 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). 如果需要它们可用,则在构建可执行文件时必须使用-rdynamic选项(这将转换为链接器的-export-dynamic选项)。 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. 或者,如果您需要将应用程序分解为单独的共享对象,则可以考虑将几乎所有内容都放入共享对象中,并使主可执行文件成为一个简单的库加载器,在所有共享对象上调用dlopen ,然后使用dlsym获取要执行的实际函数的地址,然后通过该地址调用它。 (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 .) (这基本上是我们解决Java插件问题的方式。所有Java加载的都是我们的加载程序模块,该模块然后执行dlopen 。通过明确地执行它们,我们可以控制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). 您正在通过void* ,这意味着您无权访问RTTI(甚至无法访问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. 关于void*规则很明确(这次是C ++规则, 不是 g ++):唯一可以做的事情就是将void*转换回原始指针类型。 In particular, the sequence Derived*void*Base* is undefined behavior. 特别是,序列Derived*void*Base*是未定义的行为。 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. 因此,如果共享库将A*转换为void* ,并且后来将void*转换为B* ,则您的行为不确定,因此不应期望它起作用。 Converting the void* to an A* first, and then converting it to a B* , should work. 应该先将void*转换为A* ,然后再将其转换为B* Even better, however: declare the function you're calling to return an A* , rather than a void* . 但是,更好的是:声明要调用的函数以返回A* ,而不是void* In general, you should avoid void* as much as possible, especially in C++. 通常,应尽可能避免void* ,尤其是在C ++中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM