接口方法总是虚拟的吗?

Are interface methods always virtual?

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

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

[dcc64 Error] OtlParallel.pas(846): E2170 Cannot override a non-virtual method

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

然而祖先方法声明于:

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.

Are interface methods always virtual?

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

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

编译器错误只是告诉你 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 声明为虚拟将解决错误。

Will the redeclaration of the base method in TOmniParallelSimpleLoop from non-virtual to virtual change the base type?

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

Or was the method already virtual to begin with (due to it being an implementation of an interface method)?

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

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

现在,我相信您知道,接口也是使用 VMT 实现的。但这并不意味着实现方法会自动提升为虚拟方法。一个接口就是一个VMT。当被认为是 class.

的方法时,接口 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 调用的事实并不意味着它必须以这种方式调用。

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

"A VMT is precisely an array of function pointers",在第 "Importing Objects from DLLs - The Hard Way" 部分,第 89 页。

下一节 "Importing Objects from DLLs - The Smart Way" 解释了如何使用指向抽象接口成员的函数指针数组来调用实现该接口的 DLL 以及这是如何实现的(天才之举,不只是 imo ) 提供了 Delphi 的 COM 支持的基础(一旦它通过 D2 的变体获得过去的支持)。