添加新的继承"interface"和虚拟方法需要重新编译

Adding a new inherited "interface" and virtual methods require recompile

本文关键字:新编译 编译 虚拟 继承 interface 添加 方法      更新时间:2023-10-16

现有的答案涵盖了一般情况,但它们有点模糊,我需要确定这一点。

考虑:

  • 从抽象基类"接口"派生的现有定义类
  • 类是库的一部分,该库被编译为多个通过接口相互通信的dll

然后添加:

  • 第二个"接口",定义的类现在将从中派生(所以现在它有两个接口)
  • 新接口访问的已定义类的新虚拟方法

我需要重新编译链接此库的每个dll,还是只重新编译使用新方法的dll?

编辑:

我原来的接口公开了一个动态方法Dynamic(int OP, void* args)。是否可以添加一个向新接口强制转换的op?

COM如何在不损坏现有接口的情况下向对象添加新接口?它是否堆叠接口、使用多重继承等???

让我来介绍一下它的工作方式。

静态链接库接口

In statically linked library
class Interface1
{
    virtual Method1() = 0;
    virtual Method2() = 0;
}
class NotReallyInterface2 : Interface1
{
    virtual Method1() = 0;
    virtual Method2() { // does something }
}

在dlls

In A.dll
Load statically linked library
class A : NotReallyInterface2
{
    virtual Method1() { // does something }
}
In B.dll
Load statically linked library
class B: NotReallyInterface2
{
     virtual Method1() { // does something different }
}

我想添加

class Interface3
{
     virtual Method3() = 0;
}

我在这里遇到了一些问题,因为我的继承结构看起来像。

[a.dll [ library : Interface1 < NotReallyInterface2 ] < A ]
[b.dll [ library : Interface1 < NotReallyInterface2 ] < B ]

所以我担心

[ a.dll [ library : Interface1 < NotReallyInterface2 ] < Interface3 < A ]

不会起作用。

编辑2

所以我发现了我的问题。显然,其他dll和可执行文件正在引用我的NotReallyInterface2。这意味着多个dll和ex正在构建同一个基类。因此,如果这些基本类的"副本"不同步,飞船就会坠毁。这意味着我不能更改NotReallyInterface2中的单个方法签名。

如果没有人引用NotReallyInterface2,这本可以奏效,我现在从答案中得到了这一点,整个事情都有意义。

您需要重新编译那些直接引用派生类的DLL。那些只通过接口引用它的人将继续工作。

COM依赖于这个东西。COM ABI规范有效地要求每个兼容的C++编译器不要以使接口停止工作的方式使用vtables。这就是为什么COM的一个基本要求是永远不要通过添加/删除/更改函数或为其提供新的基本接口来修改已发布的接口。

通过派生旧接口来添加新接口,并使实现类从新接口派生并不会破坏这一点;实现类中许多接口的多重继承也不起作用。

不那么抽象的类不应该成为障碍,但现在你已经超出了COM的保证范围。如果该类有数据成员,情况会更糟。我认为不重新编译代码仍然是安全的,但我不想再依赖它了。

官方表示,对任何类的任何更改都需要重新编译与该类相关的所有内容。C++标准和大多数编译器制造的文档都没有对"如果更改类中的某些内容会发生什么"做出任何保证。

在实践中,你可以做一些事情来实现这一点。你可以做的事情肯定会让一切都崩溃。

第二个接口类将引入第二个vtable,这反过来意味着继承这两个类的类存在差异。这几乎可以肯定是在"打破一切类别"中,它会在任何地方引起问题,在任何关心类的"内容"的地方使用该类。

是否可以添加一个从原始接口类派生的新类?

所以不是:

 class Interface_A
 {
   public:
    virtual void func1();
    virtual int  func2();
    ...
 };
 class Interface_B
 {
   public:
    virtual int func6();
    ...
 };
 class myClass : public Interface_A, public Interface_B
 {
   ... 
 };

这样做:

 class Interface_B : public Interface_A
 {
   public:
    virtual int func6();
    ...
 };

 class myClass : public Interface_B
 {
   ... 
 };

这将(在大多数情况下)使vtable变长一点,这对代码的其他部分来说更容易接受,并且对于任何只使用Interface_a功能的代码来说,根本不会引起任何问题。[取决于编译器是否做了明智的工作-如果你这样做,标准仍然允许编译器"把一切都搞砸"。但我曾经在一家公司工作,那里有很多代码依赖于系统的其他部分"不变",我们对这类事情进行了大量分析和处理]。

添加一个新接口会添加额外的虚拟方法,从而更改vtable(内部编译器生成的表,用于路由虚拟方法调用)的布局。因此,您需要重新编译使用该类的每个模块(或至少重新编译在该类上创建/销毁或调用虚拟方法的每个模块)

假设第二个接口不重复来自第一个接口的任何方法完整签名(在这种情况下,您应该使用虚拟继承)并且在您的实现中,第二个界面实际上是第二个:

ImplClass : public Interface1, Interface2

您不需要重新编译只使用Interface1的现有代码。这是因为ImplClass现在将有两个指向两个vtable的指针,但第一个指针将保留在该类内存布局的开头。

此外,如果您在库中使用工厂方法,这意味着客户端代码总是通过调用库中实现的Interface1*CreateInterface1()方法来获得Interface*(并且从未直接处理ImplClass*),而现在该方法被重新编译,那么您甚至不关心接口顺序。