[英]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,同时Value
和FHovered
都可以指向同一个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
接口,但从TFrame
的TControl
也声明了它。 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 类
TControl
和TFrame
中发生的事情的小示例
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.