4

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

考虑:

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

然后加:

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

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

编辑:

我的原始界面公开了一个动态方法,是否Dynamic(int OP, void* args)可以添加一个转换为新界面的操作?

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

让我列出它的工作方式。

静态链接库接口

In statically linked library

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

在 dll 中

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 和 exe 正在构建相同的基类。因此,如果这些基类的“副本”不同步,那么这艘船就会失败。这意味着我无法更改NotReallyInterface2.

如果没有人提及NotReallyInterface2,这将起作用,我现在从答案中得到了这一点,整个事情都说得通了。

4

4 回答 4

4

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

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

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

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

于 2013-08-08T16:32:27.687 回答
2

正式地,对任何类的任何更改都需要重新编译涉及该类的所有内容。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 功能的任何代码,根本不会引起任何问题。[取决于编译器做出明智的工作 - 如果你这样做,标准仍然允许编译器“搞砸一切”。但我曾经在一家公司工作,我们有很多代码依赖于系统的其他部分“没有改变”,我们对这类事情进行了相当多的分析和处理]。

于 2013-08-08T16:33:34.470 回答
1

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

于 2013-08-08T16:24:50.667 回答
0

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

ImplClass : public Interface1, Interface2

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

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

于 2013-08-08T16:58:38.483 回答