首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi 介面程式設計兩大陷阱:型別轉換與生命週期管理深度解析

     編輯:Delphi

陷阱一:介面型別轉換的隱性規則

規則 A:聲明實作與實際實作的落差

無法將物件參考強制轉換為其靜態類型未聲明實作的介面,即使物件在執行期確實實作該介面。編譯器僅依靜態型別檢查。

規則 B:物件↔介面雙向轉換的位址偏移

物件參考轉介面參考時,編譯器會調整指標位址(指向介面虛函數表);介面轉回物件時,位址不會自動調回,導致後續存取非法位址。

程式碼示範

type
  I1 = interface
    function DoIt: Boolean;
  end;
  TC1 = class
    ATT1: Integer;
  end;
  TC2 = class(TC1, I1)
    ATT2: Integer;
    function DoIt: Boolean;
  end;

var
  Intf1: I1;
  OBJ1: TC1;
  OBJ2: TC2;

OBJ2 := TC2.Create;
OBJ1 := OBJ2;           // 合法:子類指派給父類
I1(OBJ2).DoIt;          // 合法:OBJ2 靜態類型聲明實作 I1
I1(OBJ1).DoIt;          // 編譯失敗:TC1 未聲明實作 I1

// 危險的雙向轉換
OBJ2 := TC2.Create;
OBJ2.ATT1 := 0;
Intf1 := OBJ2;          // 物件→介面:位址偏移
OBJ2 := Intf1;          // 介面→物件:位址未偏回!
TC2(Intf1).ATT1 := 0;   // 運行期非法位址存取
OBJ2.ATT1 := 0;         // 同樣崩潰

陷阱二:介面生命週期管理的反直覺行為

IInterface 三大必實作方法

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;

Delphi 要求介面實作參考計數機制,否則無法正確管理物件存活。

編譯器隱性插入的 _IntfClear

介面變數設為 nil 時,編譯器自動呼叫 _IntfClear → _Release。若物件已手動 Free,_Release 存取已釋放記憶體導致 AV 錯誤:

OBJ2 := TC2.Create;
try
  Intf1 := OBJ2;
  Intf1.DoIt;
finally
  OBJ2.Free;
  Intf1 := nil;  // 觸發 _Release → 存取已釋放物件 → AV
end;

即使省略 Intf1 := nil 也會崩潰

編譯器「聰明地」在 try-finally 末尾自動插入 Intf1 := nil,同樣觸發 _Release。

解決方案

方法 1:先清介面,再釋放物件

Intf1 := nil;
OBJ2.Free;

方法 2:指標強制清零(推薦)

Pointer(Intf1) := nil;

繞過 _IntfClear,直接歸零指標,不觸發參考計數,順序無關。

容器選擇策略

  • TInterfaceList:Free 時會將元素設為 nil → 觸發 _Release,不適用。
  • 動態陣列:編譯器清理時同樣逐元素置 nil,風險同在。
  • TList(最佳):儲存純指標。加入時 InterList.Add(Pointer(I1(Obj)));釋放時無特殊處理。但必須確保轉介面後再轉指標,否則虛函數表位址錯誤。

關於 TInterfacedObject 的警示

其 _Release 會在計數歸零時呼叫 Destroy,介面置 nil 可能意外銷毀物件,除非刻意設計共享所有權,否則不建議繼承,優先選擇 TComponent(_Release 非 COM 物件時回傳 -1,不執行銷毀)。

根源總結

Delphi 介面機制源自 COM 模型,背負歷史包袱導致語言層面行為與純 OOP 直覺脫節。理解「介面指標≠物件指標」與「編譯器自動參考計數」是避坑關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved