Dynamic_cast使共享库出现问题

时间:2014-04-30 08:21:39

标签: c++ inheritance gcc shared-libraries

我在共享库创建的对象上遇到一些dynamic_cast时遇到了麻烦:

架构类似于:

class A;
class B : virtual public A; // one of the several interfaces 
class C : public B; // defined only in the shared library
  1. 主应用程序加载共享库并请求新的 对象
  2. 共享库创建一个新的C对象,但它返回一个 dynamic_cast<A*>(pointerToCclass)因为主要的应用是 不知道C
  3. 当主应用程序尝试向下转换为B时 从返回的A指针失败。
  4. 我怀疑在main和共享代码中创建的vtable有些不同可能是原因。无论如何,最初我不知道这个问题,因为主要的应用程序 调用方法void * A::getInterface( int ifEnum ),因此通过共享库代码成功执行向下转换,并作为void指针返回。然后主应用程序执行reinterpret_cast以将void指针绑定到所需的接口。

    直到现在,当多继承模式(当C实现多个接口时)似乎不稳定并导致分段错误时,所有工作都一直有效。

    我的嫌犯是真的吗? 有一种更好的方法或众所周知的方法来实现类似的架构吗?

    谢谢


    我附上了我的真实应用程序的一些简化代码和基本的演员。 首先是常见的定义:

    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
    };
    

    共享库的标题(实际上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
    

    在共享库的主体中:

    C obj;
    A * getAobject() { return dynamic_cast<A*>(&obj); } // equivalent to return &obj;
    

    最后,在主要应用程序中:

    // 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
    

1 个答案:

答案 0 :(得分:4)

如何加载共享对象? g ++使用的地址 解决dynamic_cast的RTTI信息。传统上,通过 在Unix中默认,要加载的第一个符号将被使用 所有的共享对象,所以没有问题。这个 但是,取决于dlopen中使用的模式;如果RTLD_LOCAL 指定了该共享对象中的符号(以及共享对象中的符号) 由于加载而隐式加载的对象 共享对象)在共享对象外部不可见。 (我在Java插件中遇到过这个问题.Java会加载 共享对象RTLD_LOCAL,而dynamic_cast将不起作用 共享对象隐式加载的共享对象 Java加载。)

关于主可执行文件:大多数Unix链接器都会生成 可用的符号,就像可执行文件已加载一样 使用RTLD_GLOBAL。大多数,但不是全部; GNU链接器,用于 例如,执行此操作,以及主可执行文件中的符号 不适用于共享对象。如果你需要它们 可用时,您必须在构建时使用-rdynamic选项 可执行文件(转换为-export-dynamic选项 链接器)。或者,如果你需要打破你的 您可以将应用程序分解为单独的共享对象 考虑将几乎所有东西放在共享对象中,以及 使主要可执行文件只是一个简单的库加载器, 在所有共享对象上调用dlopen,然后dlsym 获取要执行的实际函数的地址, 并通过地址调用它。 (这基本上就是我们的方式 解决了Java插件的问题。所有Java加载了 我们的加载器模块,然后执行dlopen。通过做它们 显然,我们可以控制dlopen的选项。)

编辑:

在重读你的问题时,我不确定这是对的 回答。您正在通过void*,这意味着您 无法访问RTTI(甚至无法访问vtable)。规则 关于void*(C ++规则,这次, g ++)很清楚: 你可以用void*做的唯一事情是将它转换回来 原始指针类型。特别是序列 Derived*void*Base*是未定义的行为。 如果仅涉及单个继承,它通常工作 (但即便如此,它仍然是未定义的行为),但不是 除此以外。因此,如果共享对象将A*转换为 一个void*void*后来转换为B*,你 有不确定的行为,不应指望它的工作。 首先将void*转换为A*,然后转换它 到B*,应该工作。然而,更好的是:宣布 函数你打电话来返回A*,而不是 一个void*。一般来说,你应该避免void* 可能,特别是在C ++中。