【问题标题】:Dynamic_cast troubles over shared libraries共享库上的 Dynamic_cast 问题
【发布时间】:2014-04-30 08:21:38
【问题描述】:

我在共享库创建的对象上遇到了一些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 指针失败。

我怀疑在主代码和共享代码中创建的 vtable 存在一些差异可能是原因。无论如何,最初我没有意识到这个问题,因为主应用程序 调用方法void * A::getInterface( int ifEnum ),所以共享库代码成功执行向下转换,并作为空指针返回。然后主应用程序执行 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

【问题讨论】:

  • 为什么会有从 C 到 A 的不必要的动态转换?
  • @DieterLücking 动态转换是隐式的,但(我认为)始终是动态转换,所以我宁愿不要省略它。
  • 将指针转换的精确序列显示,包括隐式和显式,作为代码(不是散布在问题中的口头描述)。
  • @n.m.是的。这里至少可能涉及两个问题,一个是纯粹的 g++/gcc,另一个是 C++。如果不确切知道他在做什么,很难说哪个有问题(但坦率地说,在他的情况下,我会同时解决这两个问题)。

标签: c++ inheritance gcc shared-libraries


【解决方案1】:

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

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

编辑:

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

【讨论】:

  • 我使用 hndl = dlopen( pathname, RTLD_NOW ),共享库有一个 C 的静态实例,由在 dlsym( hnld, "getAobject" ) 之后调用的函数 A * getAobject() 检索。但是我将使用这个 A 指针来调用 A->Create() 来分配一个新的 C 对象,我将在主应用程序中实际使用这个对象。
  • 我正在编写一些额外的代码。同时,C 类可以实现多个接口,所以我使用 void 指针来避免为每个接口定义一个函数。我将尝试对祖先类的附加步骤并让您知道...
  • @Cappelli 我不确定我是否赞同这个设计,但如果你真的只想要一个功能,你可以考虑使用标记接口:一个除了虚拟之外什么都没有的抽象类析构函数:从它(实际上)派生出所有东西,并返回一个指向它的指针。
  • @Cappelli 另外:我会将-rdynamic 添加到您的主链接中,或者在dlopen 中添加RTLD_GLOBAL。即使它们目前没有引起问题,这些步骤也可以避免将来出现问题。
  • 函数 getInterface 将指向 C 的指针转换为 B1 或 B2,然后转换为 void。在 main 中,void 指针根据请求转换回 B1 或 B2。那么,我还能认为我的代码技术上正确吗?
猜你喜欢
  • 1970-01-01
  • 2015-12-11
  • 2012-01-14
  • 2015-11-21
  • 2011-11-20
  • 1970-01-01
  • 1970-01-01
  • 2019-06-22
  • 1970-01-01
相关资源
最近更新 更多