【问题标题】:Are interface methods always virtual?接口方法总是虚拟的吗?
【发布时间】:2016-07-08 08:54:51
【问题描述】:

编译以下代码时出现错误:

TOmniParallelSimplePooledLoop = class(TOmniParallelSimpleLoop)
  procedure Execute(loopBody: TOmniIteratorSimpleSimpleDelegate); overload; override;

[dcc64 错误] OtlParallel.pas(846): E2170 无法覆盖非虚拟方法

如果我将祖先方法设为虚拟,那么错误就会消失。

然而祖先方法声明在:

IOmniParallelSimpleLoop
  ...
  procedure Execute(loopBody: TOmniIteratorSimpleSimpleDelegate); overload;

TOmniParallelSimpleLoop 中的基方法从非虚拟重新声明为虚拟会改变基类型,还是该方法一开始就已经是虚拟的(因为它是接口方法的实现)?

换句话说:当接口方法从非虚拟变为虚拟时,编译器会输出不同的代码吗?

用于重新创建错误的基本 MSVC

program Project70;
{$APPTYPE CONSOLE}
uses
  System.SysUtils;

type
  I1 = interface
    procedure DoSomething;
  end;

  T1 = class(TInterfacedObject, I1)
    procedure DoSomething;
  end;

  T2 = class(T1)
    procedure DoSomething; override;
  end;

procedure T1.DoSomething;
begin
  WriteLn('parent');
end;

procedure T2.DoSomething;
begin
  Writeln('Child');
end;

begin
end.

【问题讨论】:

  • 您应该问的真正问题是 Delphi 如何编译 T1。如果 T1.DoSomething 是静态的。所有的 COM 书籍都声明 COM = VMT。那么 T1.DoSomething 是如何进入 VMT 的呢?我想这就是你的困惑发生的地方。因为它不是用于虚拟方法调用的类 VMT,而是 VMT 映射方法,无论是静态、虚拟还是动态,所以接口知道要调用什么方法……这里有一篇很棒的文章,向您展示了如何在没有对象的情况下实现接口. sergworks.wordpress.com/2012/04/01/interfaces-without-objects

标签: class delphi interface virtual


【解决方案1】:

接口方法总是虚拟的吗?

接口的方法既不是虚拟的也不是非虚拟的。该概念不适用于接口的方法。

另一方面,类的方法可以是虚拟的或非虚拟的。区别决定了方法调用的绑定方式。考虑到对象的运行时类型,虚拟方法在运行时绑定。而非虚方法是在编译时绑定的,使用对象引用的编译时类型。

编译器错误只是告诉您override 仅对虚拟方法有意义。您的代码试图在非虚拟方法上使用override。考虑这个程序,它根本不包含任何接口:

type
  T1 = class
    procedure DoSomething;
  end;

  T2 = class(T1)
    procedure DoSomething; override;
  end;

procedure T1.DoSomething;
begin
end;

procedure T2.DoSomething;
begin
end;

begin
end.

此程序无法编译,并出现与您的程序完全相同的错误。该错误与接口无关。在T1 中引入DoSomething 时将其声明为虚拟将解决该错误。

TOmniParallelSimpleLoop中的base方法从non-virtual重新声明为virtual会改变base type吗?

是的,它会的。它将该方法从非虚拟更改为虚拟。这意味着方法分派的执行方式不同,如上所述。这意味着该类型的 VMT 会发生变化以适应新的虚拟方法。

或者该方法一开始就已经是虚拟的(因为它是接口方法的实现)?

方法用于实现接口的一部分这一事实不会改变编译器处理它的方式。非虚方法无论是否实现接口方法,都以相同的方式实现。对于虚拟方法也是如此。为实现接口而生成的 VMT 是一个明显的问题。

详细说明,每个方法都有一个地址。调用非虚拟方法时,编译器准确地知道要调用哪个方法。因此它可以发出代码来直接调用该已知方法。对于虚方法,编译器不知道会调用哪个方法。这由运行时类型决定。因此编译器发出代码来读取对象的 VMT 中的已知条目,然后调用该方法。

现在,我相信您知道,接口也是使用 VMT 实现的。但这并不意味着实现方法会自动提升为虚拟的。接口只是一个 VMT。当被认为是类的方法时,接口 VMT 引用的方法可以是虚拟的或非虚拟的。

type
  ISomeInterface = interface
    procedure Foo;
  end;

  TSomeObject = class(TInterfacedObject, ISomeInterface)
    procedure Foo;
  end;

....

var
  Intf: ISomeInterface;
  Obj: TSomeObject;
....
Intf := TSomeObject.Create;
Obj := Intf as TSomeObject;

// non-virtual method, direct dispatch at compile time
Obj.SomeMethod; 

// interface method, dispatched via interface VMT
Intf.SomeMethod;

因此,可以通过 VMT 调用方法这一事实并不意味着它必须以这种方式调用。

【讨论】:

  • 嗯,你还没有回答这个问题:Will the redeclaration of the base method [...] from basic to virtual change the base object。 IE。将添加 virtual 关键字导致编译器为 TBaseObject 生成不同的代码。
  • “会的。”你确定吗?我有点认为不会。 (但不是 100% 确定)。
  • 100% 肯定会的。您可以检查发出的代码。方法分派完全不同。 VMT 不同。
  • 所以这意味着我可以重新声明方法并完成它。
  • 好吧,我不知道您要解决什么问题。如果你的意思是,重新声明一个非虚方法,并让它实现接口,你还需要在派生类型中重新声明接口。请记住,我不知道您的根本问题
【解决方案2】:

我认为这个问题的答案包含在 Danny Thorpe 的“Delphi 组件设计”的一句话中,ISBN 0-201-46136-6,我在对@DavidH 的回答的评论中提到:

“VMT 就是一个函数指针数组”,在“从 DLL 导入对象 - 困难的方式”部分,第 89 页。

下一节“从 DLL 导入对象 - 智能方式”解释了如何使用指向抽象接口成员的函数指针数组来调用实现该接口的 DLL,以及如何实现该接口(天才的一击,不仅仅是 imo) 提供了 Delphi 的 COM 支持的基础(一旦它通过 D2 的变体获得了过去的支持)。

【讨论】:

  • 爱丹尼的书。
  • 你描述了 COM 的天才,而不是任何特定于 Delphi 的东西
  • @DavidHeffernan:好吧,对不起,我不同意。戴尔·罗杰森 (Dale Rogerson) 的 Inside COM 描述了一系列混乱(但连贯)的解决方案,以解决一系列混乱的问题。这对我来说算不上天才,无论如何这不是镇上唯一的表演(c.f,Corba)。顺便说一句,我从 D Thorpe 引述的那段话并不是在谈论 COM。
  • Delphi 接口是 COM 接口。 COM 的设计者发明了它们。 Delphi接口是COM接口的一种实现。 VMT 早在 Delphi 之前就已经存在。关于 COM 的最佳书籍是 Don Box 的开创性著作 Essential COM。
  • Delphi 接口的一个令人沮丧的地方是它们与 COM 紧密相关。有时不必实现IUnknown 会很好。当然,您可以制作自己的 VMT。您可以创建方法指针的记录。我想要非 COM 接口的语言支持。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-22
  • 1970-01-01
  • 1970-01-01
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
  • 2021-11-02
相关资源
最近更新 更多