【问题标题】:Why interface table is not generated for the child class?为什么没有为子类生成接口表?
【发布时间】:2016-05-16 09:31:37
【问题描述】:

我有以下问题:

  • 实现某个接口的基类
  • 从基类派生并覆盖接口方法的另一个类

对于子类根本不生成接口表。

type
  ITest = interface
    ['{69068A88-6712-40E0-B1E3-DA265F7428EA}']
    procedure Test;
  end;

  TBase = class(TInterfacedObject, ITest)
  protected
    procedure Test; virtual;
  public
    constructor Create;
  end;

  TChild = class(TBase)
  protected
    procedure Test; override;
  end;

constructor TBase.Create;
begin
  Assert(GetInterfaceTable <> nil);
end;

所以当使用以下构造时:

var
  X: ITest;
begin
  X := TChild.Create;
end;

我得到断言失败。

所以我知道我需要在类声明中重新声明接口来解决这个问题。但这是语言特性还是编译器的老问题?

因为在编译时编译器知道 TChild 正在实现 ITest 接口。但是一旦我们进入运行时,我确实需要从基础重复重新声明接口!我们为什么要这样做?对我来说,它看起来很糟糕。

【问题讨论】:

  • David Heffernan 会告诉您,将接口方法声明为虚拟方法并在派生类中覆盖它与接口无关。你可以做到,但没用。如果需要更改派生类中的接口方法,则应在派生类中重新声明接口,并且更改的方法可以是静态的 - 无需将其设为虚拟。
  • 这在速度方面是有道理的,但对我来说不是。所有类共享相同的接口,并且某些类可以覆盖方法。但无论如何问题都解决了——它在 IoC 容器中,解析父类就完成了。

标签: delphi interface polymorphism


【解决方案1】:

如文档所述,GetInterfaceTable 返回类的接口条目列表。在您的情况下,TChild 本身没有实现任何接口。

返回一个指向包含所有接口的结构的指针 由给定的类实现。

GetInterfaceTable 返回类的接口条目。这 list 只包含这个类实现的接口,而不是它的 祖先。要查找祖先列表,请迭代调用 ClassParent 和 然后在它返回的值上调用 GetInterfaceTable。查找条目 对于特定接口,请改用 GetInterfaceEntry 方法。

GetInterfaceTable 是类函数,就像ClassName 是类函数一样。它取决于实例类而不是您从代码的哪一部分调用它:

如果你运行下面的代码,它会给你不同的ClassName,不管你在TBase构造函数中调用代码。

constructor TBase.Create;
begin
  writeln(ClassName);
end;

var
  x : ITest;

   X := TBase.Create; // outputs TBase
   X := TChild.Create; // outputs TChild

【讨论】:

  • 感谢您的回答。这就是我的想法,现在我可以确定了:)
【解决方案2】:

这是设计的。

AFAIR GetInterfaceTable 是一个非常底层的方法,它只在给定的类级别上工作。你不应该在你的代码中使用这个方法,除非你弄乱了低级别的 RTTI 信息......但在所有情况下,你最好不要使用它。

这里是如何实现的:

class function TObject.GetInterfaceTable: PInterfaceTable;
begin
  Result := PPointer(PByte(Self) + vmtIntfTable)^;
end;

因此,您还必须使用递归调用或循环来检查父类的类型。

例如,下面是它在 System.pas 中的使用示例:

class function TObject.InitInstance(Instance: Pointer): TObject;
var
  IntfTable: PInterfaceTable;
  ClassPtr: TClass;
  I: Integer;
begin
  FillChar(Instance^, InstanceSize, 0);
  PPointer(Instance)^ := Pointer(Self);
  ClassPtr := Self;
  while ClassPtr <> nil do
  begin
    IntfTable := ClassPtr.GetInterfaceTable;
    if IntfTable <> nil then
      for I := 0 to IntfTable.EntryCount-1 do
        with IntfTable.Entries[I] do
        begin
          if VTable <> nil then
            PPointer(@PByte(Instance)[IOffset])^ := VTable;
        end;
    ClassPtr := ClassPtr.ClassParent;
  end;
  Result := Instance;
end;

相当低级的东西,不是吗?

要实现 IoC 模式,您宁愿使用 TObject.GetInterfaceEntry(),它可以满足您的期望。

【讨论】:

  • 这用于 IoC 容器。
  • 它在调用 Supports() 时有效。可能我只需要通过 ClassParent.GetInterfaceTable...
  • 看看我们如何在 mORMot.pas 中实现 TServiceContainerServer.AddImplementation - github.com/synopse/mORMot/blob/master/SQLite3/mORMot.pas
  • @ZENsan Supports() 使用 GUID 搜索所有继承层次结构的整个 Entries[],使用 ClassPtr.ClassParent 循环 - 它实际上调用 TObject.GetInterfaceEntry() 这可能是函数你需要使用。
  • 这就是我刚刚所做的——通过父类循环。因为我需要在某种情况下注册该类实现的所有接口。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-18
  • 1970-01-01
  • 2021-01-14
  • 2017-08-07
  • 1970-01-01
  • 2014-03-07
相关资源
最近更新 更多