【问题标题】:Adding a new inherited "interface" and virtual methods require recompile添加新的继承“接口”和虚拟方法需要重新编译
【发布时间】:2013-08-10 11:12:10
【问题描述】:

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

考虑:

  • 从抽象基类“接口”派生的现有定义类。
  • 类是库的一部分,它被编译成多个 dll,这些 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,这会奏效,我现在从答案中得到了这一点,整个事情都说得通了。

【问题讨论】:

  • 这就是为什么尽早让您的接口正确非常重要的原因。
  • @NeilKirk 这是一个已建立接口的问题,我需要在该类中添加新功能并能够访问它们。
  • COM 是一个完全不同的问题。记住 COM 是跨语言的(VB 和 C++ 以及 C# 应用程序都可以使用 COM 对象。)“COM 是如何做到的?”的完整答案问题不仅仅是对 StackOverflow 的评论。阅读 IUnknown 并了解它的作用以及他们为什么需要它。

标签: c++ dll interface


【解决方案1】:

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

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

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

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

【讨论】:

  • 这与其他答案冲突。你有证据支持你的回答吗?
  • 这是对 Mats Petersson 答案的简化 - 他为您提供了一些关于您可以做的具体事情的指导方针,以避免重新编译更多代码。所以我的回答和他的不冲突,只是比较保守。
  • 至于 Dale Wilson 的激进观点“编译器可以搞砸它想要的任何东西”,从 C++ 标准的角度来看确实如此,但是您使用的是 DLL,无论如何这与该观点不兼容.但是,当实现类更改时,仅使用接口的代码不需要重新编译的保证直接来自 COM 的工作方式。 COM 被设计成可以重新编译组件,并且只使用接口的代码将继续工作。这是一个 ABI 保证,每个可用于 COM 的编译器都必须提供。当然,这几乎是所有 WIndows 编译器。
  • 那么使用QueryInterface来安全地转换成不同的接口?
  • 如果我有一些半抽象的课程怎么办?
【解决方案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 功能的任何代码,根本不会造成任何问题。 [取决于编译器做出明智的工作 - 如果您这样做,该标准仍然允许编译器“搞砸一切”。但是我曾经在一家公司工作,我们有很多代码依赖于系统的其他部分“没有改变”,我们对这类事情进行了相当多的分析和处理]。

【讨论】:

  • 不幸的是我不能这样做,因为有一个半抽象的基类。我明白为什么 COM 需要一个 QueryInterface 方法。
【解决方案3】:

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

【讨论】:

  • 通过“如果您想对实际工作的应用程序有任何信心”来限定我的回答正如其他人指出的那样,您可以作弊并假设编译器实现细节。你通常会侥幸逃脱,但并非总是如此。 (幽默)请不要在任何影响我或我的任何朋友和亲戚的软件上这样做。 (/幽默)
  • 您确实意识到在 Windows 上使用 COM 的每个程序(即可能每个不平凡的程序,在引擎盖下的某个地方)都在这样做,对吗?此外,“影响”;-p
  • COM 不使用 C++ 虚方法来实现其“继承”(实际上是聚合)
  • 嗯? COM 二进制规范是在 Microsoft C++ 虚拟分派机制之后建模的,这就是您在 C++ 中实现组件的方式。你到底在说什么?
  • 了解 IUnknown。这是 C++ 中不存在的概念。这个原始问题是关于 C++ 的,如果是关于在 COM 中添加接口的问题,那么我的答案是不同的。但不要仅仅因为它们使用相同的名称,就误以为 COM 接口与仅包含纯虚拟方法(即接口)的 C++ 类在任何方面都是相同的。
【解决方案4】:

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

ImplClass : public Interface1, Interface2

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

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

【讨论】:

  • 我这个不那么抽象的类会不会碍事?
  • 抱歉,我不确定我是否理解您的最后一个问题。能否请您重新措辞一下?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-11
  • 1970-01-01
  • 2015-09-09
  • 1970-01-01
  • 2011-07-16
  • 1970-01-01
相关资源
最近更新 更多