简体   繁体   English

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

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

In the source code of Delphi, I see this in the FMX.Forms unit:在 Delphi 的源代码中,我在FMX.Forms单元中看到了这个:

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

I think doing Value <> FHovered is fundamentally wrong because Value <> FHovered can return true and at the same time both Value and FHovered can point to the same TControl object.我认为做Value <> FHovered是根本错误的,因为Value <> FHovered可以返回 true,同时ValueFHovered都可以指向同一个TControl object。 Am I wrong?我错了吗? (note this is what I saw in debugging). (注意这是我在调试中看到的)。

Now a subsidiary question: why can 2 IControl interfaces be different (from the view of pointers) but point to the same TControl ?现在有一个附属问题:为什么 2 个IControl接口可以不同(从指针的角度来看)但指向同一个TControl

Note: below a sample that show how 2 IControl can be different (from the pointer view) and still pointing to the same object:注意:下面的示例显示了 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;

Now also what could be the good way to fix this bug?现在还有什么可以修复这个错误的好方法?

Problem with directly comparing interfaces is that each class can declare interface even if it was already declared in ancestor.直接比较接口的问题是每个 class 都可以声明接口,即使它已经在祖先中声明。 That allows that redeclared interface can implement different methods in the derived class.这允许重新声明的接口可以在派生的 class 中实现不同的方法。

Every object instance has associated metadata attached, interface table.每个 object 实例都有关联的元数据,接口表。 Interface table contains list of pointers for each declared interface pointing to the virtual method table for that particular interface.接口表包含指向该特定接口的虚拟方法表的每个已声明接口的指针列表。 If the interface is declared more than once, each declaration will have its own entry in the interface table pointing to its own VMT.如果接口被多次声明,则每个声明在接口表中都有自己的条目,指向自己的 VMT。

When you take interface reference of particular object instance, value in that reference is the appropriate entry from that object's interface table.当您获取特定 object 实例的接口引用时,该引用中的值是该对象接口表中的相应条目。 Since that table may contain multiple entries for the same interface, those values can be different even though they belong to the same object.由于该表可能包含同一接口的多个条目,因此即使它们属于同一个 object,这些值也可能不同。

In context of Firemonkey, TControl declares IControl interface, but TFrame which descends from TControl also declares it.在 Firemonkey 的上下文中, TControl声明了IControl接口,但从TFrameTControl也声明了它。 Which means TFrame instances will have two different entries for IControl interface in their interface table.这意味着TFrame实例在其接口表中将有两个不同的IControl接口条目。

TControl = class(TFmxObject, IControl, ...

TFrame = class(TControl, IControl)

TFrame redeclares the IControl interface because it implements different GetVisible method, which is declared as non virtual in ancestor class for the purpose of the Form Designer. TFrame重新声明了IControl接口,因为它实现了不同GetVisible方法,该方法在祖先 class 中声明为非虚拟,以用于表单设计器。

If each class in FMX hierarchy would declare IControl only once, then simple comparison like the one in SetHovered would work properly.如果 FMX 层次结构中的每个 class 只声明一次IControl ,那么像SetHovered中的简单比较将正常工作。 But if not, then it is possible that comparison will return true for the same object.但如果不是,那么对于相同的 object,比较可能会返回 true。

Solution is either removing additional interface declaration which would also require implementing GetVisible as virtual, or typecasting interfaces to objects and comparing objects, or typecasting to IUnknown , but typecasting is slower solution from performance point of view.解决方案是删除额外的接口声明,这也需要将GetVisible实现为虚拟,或者将接口类型转换为对象并比较对象,或者类型转换为IUnknown ,但从性能角度来看,类型转换是较慢的解决方案。 However, typecasting to object or IUnknown is the best fast fix because it cannot possibly break anything else and it is not interface breaking change.然而,类型转换为 object 或IUnknown是最好的快速修复,因为它不可能破坏其他任何东西并且它不是接口破坏性更改。

Small example that demonstrates what is going on in FMX classes TControl and TFrame演示 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;

If you run the above code the output will be:如果您运行上述代码,output 将是:

FALSE
TRUE
TRUE
BASE
DERIVED

Which shows that Intf1 and Intf2 when compared directly as pointers are different.这表明直接作为指针比较时 Intf1 和 Intf2 是不同的。 When casted back to the owning object instance they point to the same object.当转换回拥有的 object 实例时,它们指向相同的 object。 And when compared following the COM guidelines for which states the same COM object must return the same interface for IUnknown they are equal (backed by the same object).并且当遵循 COM 指南进行比较时,该指南规定相同的 COM object 必须为IUnknown返回相同的接口,它们是相等的(由相同的对象支持)。

IUnknown QueryInterface 未知查询接口

For any given COM object (also known as a COM component), a specific query for the IUnknown interface on any of the object's interfaces must always return the same pointer value.对于任何给定的 COM object(也称为 COM 组件),对任何对象接口上的 IUnknown 接口的特定查询必须始终返回相同的指针值。 This enables a client to determine whether two pointers point to the same component by calling QueryInterface with IID_IUnknown and comparing the results.这使客户端能够通过使用 IID_IUnknown 调用 QueryInterface 并比较结果来确定两个指针是否指向同一个组件。 It is specifically not the case that queries for interfaces other than IUnknown (even the same interface through the same pointer) must return the same pointer value.对于 IUnknown 以外的接口(甚至通过相同指针的相同接口)的查询并非必须返回相同的指针值。

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

相关问题 比较和对比Java和Delphi中的接口 - Compare and contrast interfaces in Java and Delphi 使用BDE和Delphi访问dBase文件的好方法是什么? - What is a good way of accessing dBase files using BDE and Delphi? 在Delphi中使用接口的优缺点是什么? - What are the pros and cons of using interfaces in Delphi? Delphi 3支持接口中的等效功能是什么? - What's the equivalent in Delphi 3 of Supports for Interfaces? 将Delphi对象树序列化为XML的好方法是什么 - 使用RTTI而不是自定义代码? - What's a good way to serialize Delphi object tree to XML--using RTTI and not custom code? 解决Delphi 2010或Delphi 2009中的TDatasetProvider中的错误(困难的方式) - Resolving a bug in the TDatasetProvider (the hard way) in Delphi 2010 or Delphi 2009 在Delphi 2010中创建PDF的好库是什么? - What is a good library for creating PDFs in Delphi 2010? 什么是Delphi System单元中的TMonitor有用? - What is TMonitor in Delphi System unit good for? 以多线程方式使用Delphi7 COM接口时的内存消耗 - Memory consumption when using Delphi7 COM interfaces in a multithreaded way 在Delphi和Lazarus(FPC)中实现接口有什么区别? - What are the differences between implementation of Interfaces in Delphi and Lazarus (FPC)?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM