添加新的继承“接口”和虚拟方法需要重新编译

时间:2013-08-08 16:22:06

标签: c++ dll interface

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

考虑:

  • 从抽象基类“interface”派生的现有定义类。
  • Class是库的一部分,它被编译成多个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和exes正在构建相同的基类。因此,如果基类的那些“副本”不同步,那么就会失败。这意味着我无法在NotReallyInterface2中更改单个方法签名。

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

4 个答案:

答案 0 :(得分:4)

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

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

通过派生旧接口并使实现类派生自新接口来添加新接口不会破坏它;实现类中的许多接口都没有多重继承。

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

答案 1 :(得分: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
 {
   ... 
 };

那会(在大多数情况下)jut使vtable更长一些,这对于其余代码来说更容易接受,并且对于只使用Interface_A功能的任何代码,都不会引起任何问题。 [根据编译器做出明智的工作 - 如果你这样做,标准仍然允许编译器“搞乱一切”。但我曾经在一家公司工作过,我们有很多代码依赖系统的其他部分“不改变”,我们分析并处理了很多这类事情。

答案 2 :(得分:1)

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

答案 3 :(得分:0)

假设第二个接口没有从第一个接口重复任何方法完全签名(在这种情况下你应该使用虚拟继承)并且在你的实现中第二个接口实际上是第二个:

ImplClass : public Interface1, Interface2

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

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