使用共享库中的多态对象安全吗?

Is it safe to use polymorphic objects from a shared library?

我有一个接口,我在共享库中有一个该接口的实现,谁知道它是用什么编译的。

dynamic_cast 仅适用于多态类型。所以我可以假设它不使用 RTTI,所以我禁用它,然后编译。

并且由于没有实现多态性的标准化方法,因此多态类型对象的布局是未知的,其 vtable 的布局也是如此。

因此出现了一个问题:我可以使用共享库中的多态类型的对象,该共享库是用与我使用的编译器不同的编译器编译的吗?而且,更重要的是,为什么?

C++ 没有标准化的 ABI,但如果您的接口遵循一些规则,您可以在 大多数 编译器之间实现 ABI 兼容性 - 特别是在 Windows 上。这种技术在 Microsoft 的 COM (https://en.wikipedia.org/wiki/Component_Object_Model), but I've also seen it in Steinberg's VST Model Architecture (https://steinbergmedia.github.io/vst3_doc/base/index.html) and in the "openvr" library (https://github.com/ValveSoftware/openvr/blob/5aa6c5f0f6520c59c4dce124541ecc62604fd7a5/headers/openvr.h#L1940) 中最为著名。

以下是此类 ABI 兼容 C++ 接口的基本规则:

  1. 只有纯虚方法。这使得多重继承相对不成问题。一个重要的警告是,只允许实现方执行从一个接口到另一个接口的 static_cast(以保证正确的 this 指针调整)。为了解决这个问题,每个COM接口都提供了QueryInterface()方法,有点类似于dynamic_cast.
  2. 没有虚拟析构函数。一些编译器生成超过 1 个 vtable 条目(参见 )。这意味着您必须实现自己的机制来从基指针中析构对象。 COM 和 VST3 使用 addRef()release() 的引用计数,它们有自己的客户端智能指针类型。或者,您可以使用简单的 destroy() 方法,并使用自定义删除器将对象实例存储在常规 std::unique_ptrstd::shared_ptr 中。
  3. 内存管理不得跨越接口边界,即您不得在一侧分配内存并在另一侧释放内存,因为每一侧都可能使用不同的运行时。该库必须提供一个自由函数或接口方法来释放对象。它还可能允许用户传递分配器,因此内存管理保留在客户端。
  4. 没有重载的虚拟方法。大多数编译器通常按照声明的顺序将方法放在 vtable 中,但显然 重载方法 在 vtable 中的顺序因编译器而异。
  5. 所有方法参数必须是原始类型或 public 类 具有稳定的对象布局(最好是 PODs)。您不得使用 C++ 标准库中的任何 类,例如 std::string,因为实现不稳定。
  6. 界面一经发布,不得更改。您可以通过继承现有接口来扩展它们:
class IFoo { 
public:
    virtual void foo() = 0; 
};

class IFooEx : public IFoo {
public:
    virtual void bar() = 0; 
};

或者添加一个新接口并使用多重继承。

也就是说,作为库作者,您在选择此技术之前应该三思而后行,尤其是当您计划为其他语言添加绑定时。尽管此类 C++ 接口的 vtable 可以转换为函数指针的 C 结构,但通常的方法是

a) 以 C API 开头并提供客户端 C++ 包装器

b) 使用常规 C++ 接口并在其上提供一个 C 层。

但是通过正确的抽象,类 COM 的 C++ 接口非常适合编程。