繁体   English   中英

比较 2 个接口(IControl)的好方法是什么? 这是 Delphi 中的错误吗?

[英]What is a good way to compare 2 interfaces (IControl)? Is this a bug in Delphi?

在 Delphi 的源代码中,我在FMX.Forms单元中看到了这个:

procedure TCommonCustomForm.SetHovered(const Value: IControl);
begin
  if (Value <> FHovered) then
  begin
    ....
  end;
end;

我认为做Value <> FHovered是根本错误的,因为Value <> FHovered可以返回 true,同时ValueFHovered都可以指向同一个TControl object。 我错了吗? (注意这是我在调试中看到的)。

现在有一个附属问题:为什么 2 个IControl接口可以不同(从指针的角度来看)但指向同一个TControl

注意:下面的示例显示了 2 IControl如何不同(从指针视图)并且仍然指向相同的 object:

procedure TForm.Button1Click(Sender: TObject);
var LFrame: Tframe;
    Lcontrol: Tcontrol;
    LIcontrol1: Icontrol;
    LIcontrol2: Icontrol;
begin
  Lframe := Tframe.Create(nil);
  Lcontrol := Lframe;
  LIcontrol1 := Lframe;
  LIcontrol2 := Lcontrol;
  if LIcontrol1 <> LIcontrol2 then
    raise Exception.Create('Boom');
end;

现在还有什么可以修复这个错误的好方法?

直接比较接口的问题是每个 class 都可以声明接口,即使它已经在祖先中声明。 这允许重新声明的接口可以在派生的 class 中实现不同的方法。

每个 object 实例都有关联的元数据,接口表。 接口表包含指向该特定接口的虚拟方法表的每个已声明接口的指针列表。 如果接口被多次声明,则每个声明在接口表中都有自己的条目,指向自己的 VMT。

当您获取特定 object 实例的接口引用时,该引用中的值是该对象接口表中的相应条目。 由于该表可能包含同一接口的多个条目,因此即使它们属于同一个 object,这些值也可能不同。

在 Firemonkey 的上下文中, TControl声明了IControl接口,但从TFrameTControl也声明了它。 这意味着TFrame实例在其接口表中将有两个不同的IControl接口条目。

TControl = class(TFmxObject, IControl, ...

TFrame = class(TControl, IControl)

TFrame重新声明了IControl接口,因为它实现了不同GetVisible方法,该方法在祖先 class 中声明为非虚拟,以用于表单设计器。

如果 FMX 层次结构中的每个 class 只声明一次IControl ,那么像SetHovered中的简单比较将正常工作。 但如果不是,那么对于相同的 object,比较可能会返回 true。

解决方案是删除额外的接口声明,这也需要将GetVisible实现为虚拟,或者将接口类型转换为对象并比较对象,或者类型转换为IUnknown ,但从性能角度来看,类型转换是较慢的解决方案。 然而,类型转换为 object 或IUnknown是最好的快速修复,因为它不可能破坏其他任何东西并且它不是接口破坏性更改。

演示 FMX 类TControlTFrame中发生的事情的小示例

type
  IControl = interface
    ['{95283CFD-F85E-4344-8577-6A6CA1C20D00}']
    procedure Print();
  end;

  TBase = class(TInterfacedObject, IControl)
  public
    procedure Print();
  end;

  TDerived = class(TBase, IControl)
  public
    procedure Print();
  end;

procedure TBase.Print;
begin
  Writeln('BASE');
end;

procedure TDerived.Print;
begin
  Writeln('DERIVED');
end;

procedure Test;
var
  Obj: TBase;
  Intf1, Intf2: IControl;
begin
  Obj := TDerived.Create;
  // Obj is declared as TBase so assigning will use IControl entry associated with TBase class
  Intf1 := Obj;
  // Typecasting to TDerived will use IControl entry associated with TDerived class
  Intf2 := TDerived(Obj);

  Writeln(Intf1 = Intf2);
  Writeln(TObject(Intf1) = TObject(Intf2));
  Writeln(Intf1 as IUnknown = Intf2 as IUnknown);

  Intf1.Print;
  Intf2.Print;
end;

如果您运行上述代码,output 将是:

FALSE
TRUE
TRUE
BASE
DERIVED

这表明直接作为指针比较时 Intf1 和 Intf2 是不同的。 当转换回拥有的 object 实例时,它们指向相同的 object。 并且当遵循 COM 指南进行比较时,该指南规定相同的 COM object 必须为IUnknown返回相同的接口,它们是相等的(由相同的对象支持)。

未知查询接口

对于任何给定的 COM object(也称为 COM 组件),对任何对象接口上的 IUnknown 接口的特定查询必须始终返回相同的指针值。 这使客户端能够通过使用 IID_IUnknown 调用 QueryInterface 并比较结果来确定两个指针是否指向同一个组件。 对于 IUnknown 以外的接口(甚至通过相同指针的相同接口)的查询并非必须返回相同的指针值。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM