在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物件。我錯了嗎?(注意這是我在除錯中看到的)。
現在是一個附屬問題:為什么 2 個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;
現在還有什么可能是修復此錯誤的好方法?
uj5u.com熱心網友回復:
直接比較介面的問題是每個類都可以宣告介面,即使它已經在祖先中宣告過。這允許重新宣告的介面可以在派生類中實作不同的方法。
每個物件實體都有關聯的元資料附加,介面表。介面表包含指向該特定介面的虛擬方法表的每個宣告介面的指標串列。如果介面被多次宣告,則每個宣告在介面表中都有自己的條目,指向自己的 VMT。
當您獲取特定物件實體的介面參考時,該參考中的值是該物件介面表中的相應條目。由于該表可能包含同一介面的多個條目,因此即使它們屬于同一物件,這些值也可能不同。
在 Firemonkey 的背景關系中,TControl宣告IControl介面,但TFrame其后代TControl也宣告它。這意味著TFrame實體IControl在其介面表中將有兩個不同的介面條目。
TControl = class(TFmxObject, IControl, ...
TFrame = class(TControl, IControl)
TFrame重新宣告IControl介面,因為它實作了不同的GetVisible方法,為了表單設計器的目的,在祖先類中將其宣告為非虛擬的。
如果 FMX 層次結構中的每個類IControl只宣告一次,那么像 in 中的一個這樣的簡單比較SetHovered就可以正常作業。但如果不是,那么對于同一個物件,比較可能會回傳 true。
解決方案是洗掉額外的介面宣告,這也需要實作GetVisible為虛擬,或將介面型別轉換為物件并比較物件,或型別轉換為IUnknown,但從性能角度來看,型別轉換是較慢的解決方案。然而,型別轉換到 object orIUnknown是最好的快速修復,因為它不可能破壞其他任何東西,而且它不是介面破壞性更改。
演示 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;
如果運行上面的代碼,輸出將是:
FALSE
TRUE
TRUE
BASE
DERIVED
這表明直接作為指標比較時 Intf1 和 Intf2 是不同的。當轉換回擁有的物件實體時,它們指向同一個物件。當遵循 COM 準則進行比較時,相同的 COM 物件必須回傳相同的介面,因為IUnknown它們是相等的(由相同的物件支持)。
IUnknown QueryInterface
對于任何給定的 COM 物件(也稱為 COM 組件),對任何物件介面上的 IUnknown 介面的特定查詢必須始終回傳相同的指標值。這使客戶端能夠通過使用 IID_IUnknown 呼叫 QueryInterface 并比較結果來確定兩個指標是否指向同一組件。對于 IUnknown 以外的介面(即使是通過相同指標的相同介面)的查詢必須回傳相同的指標值,具體情況并非如此。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/386632.html
