[英]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.