[英]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,同时Value
和FHovered
都可以指向同一个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
接口,但从TFrame
的TControl
也声明了它。 这意味着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 类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;
如果您运行上述代码,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.