簡體   English   中英

比較 2 個接口(IControl)的好方法是什么? 這是 Delphi 中的錯誤嗎?

[英]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,同時ValueFHovered都可以指向同一個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接口,但從TFrameTControl也聲明了它。 這意味着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 類TControlTFrame中發生的事情的小示例

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM