在Delphi的源代码中,我在FMX.Forms单元中看到了这样的内容:
procedure TCommonCustomForm.SetHovered(const Value: IControl);
begin
if (Value <> FHovered) then
begin
....
end;
end;
Run Code Online (Sandbox Code Playgroud)
我认为这样做Value <> FHovered 从根本上来说是错误的,因为Value <> FHovered可以返回 true 并且同时两者都Value可以FHovered指向同一个TControl对象。我错了吗?(注意这是我在调试中看到的)。
现在有一个附属问题:为什么两个IControl接口可以不同(从指针的角度来看)但指向相同TControl?
注意:下面的示例显示 2 如何IControl不同(从指针视图)但仍然指向同一个对象:
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;
Run Code Online (Sandbox Code Playgroud)
现在修复这个错误的好方法是什么?
直接比较接口的问题是每个类都可以声明接口,即使它已经在祖先中声明了。这使得重新声明的接口可以在派生类中实现不同的方法。
每个对象实例都附有关联的元数据、接口表。接口表包含每个声明的接口的指针列表,这些指针指向该特定接口的虚拟方法表。如果接口被声明多次,则每个声明都会在接口表中拥有自己的条目,指向其自己的 VMT。
当您获取特定对象实例的接口引用时,该引用中的值是该对象接口表中的相应条目。由于该表可能包含同一接口的多个条目,因此即使它们属于同一对象,这些值也可能不同。
在 Firemonkey 的上下文中,TControl声明了IControl接口,但TFrame它的后代TControl也声明了它。这意味着TFrame实例在其接口表中将有两个不同的IControl接口条目。
TControl = class(TFmxObject, IControl, ...
TFrame = class(TControl, IControl)
Run Code Online (Sandbox Code Playgroud)
TFrame重新声明IControl接口,因为它实现了不同的GetVisible方法,出于表单设计器的目的,该方法在祖先类中声明为非虚拟方法。
如果 FMX 层次结构中的每个类仅声明IControl一次,则可以进行简单的比较,如SetHovered就可以正常工作。但如果不是,那么对于同一个对象,比较可能会返回 true。
解决方案是删除额外的接口声明(这也需要实现GetVisible为虚拟),或者将接口类型转换为对象并比较对象,或者类型转换为IUnknown,但从性能角度来看,类型转换是较慢的解决方案。然而,类型转换为对象或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;
Run Code Online (Sandbox Code Playgroud)
如果运行上面的代码,输出将是:
FALSE
TRUE
TRUE
BASE
DERIVED
Run Code Online (Sandbox Code Playgroud)
这表明 Intf1 和 Intf2 直接作为指针比较时是不同的。当转换回拥有对象实例时,它们指向同一个对象。与遵循 COM 指南相比,相同的 COM 对象必须返回相同的接口IUnknown它们是相等的(由相同的对象支持)。
对于任何给定的 COM 对象(也称为 COM 组件),对任何对象接口上的 IUnknown 接口的特定查询必须始终返回相同的指针值。这使得客户端能够通过使用 IID_IUnknown 调用 QueryInterface 并比较结果来确定两个指针是否指向同一组件。具体来说,查询除 IUnknown 之外的接口(即使是通过相同指针的相同接口)必须返回相同的指针值。
| 归档时间: |
|
| 查看次数: |
331 次 |
| 最近记录: |