Delphi 介面變量(IInterface 及其衍生)基於 COM 引用計數:_AddRef 增計數,_Release 減計數,**計數歸零即調用對象 Destroy**。TInterfacedObject 內建此邏輯,使介面變量擁有「智能指針」語義。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
ITest = interface
procedure DoWork;
end;
TTestObj = class(TInterfacedObject, ITest)
public
constructor Create;
destructor Destroy; override;
procedure DoWork;
end;
TForm1 = class(TForm)
btn1, btn2, btn3, btn4: TButton;
procedure btn1Click(Sender: TObject); // 對象變量手動 Free
procedure btn2Click(Sender: TObject); // 介面變量自動釋放
procedure btn3Click(Sender: TObject); // 對象+介面雙持有
procedure btn4Click(Sender: TObject); // 介面手動置 nil 提前釋放
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TTestObj.Create;
begin
inherited;
ShowMessage('TTestObj.Create');
end;
destructor TTestObj.Destroy;
begin
ShowMessage('TTestObj.Destroy');
inherited;
end;
procedure TTestObj.DoWork;
begin
ShowMessage('ITest.DoWork');
end;
{ 1. 純對象變量:手動管理 }
procedure TForm1.btn1Click(Sender: TObject);
var
Obj: TTestObj;
begin
Obj := TTestObj.Create; // Create
Obj.DoWork; // DoWork
Obj.Free; // Destroy (手動)
ShowMessage('**********');
end;
{ 2. 純介面變量:自動管理 }
procedure TForm1.btn2Click(Sender: TObject);
var
Intf: ITest;
begin
Intf := TTestObj.Create; // Create
Intf.DoWork; // DoWork
// 無需手動釋放
ShowMessage('**********');
// 過程結束,Intf 離開作用域 → _Release → Destroy
end;
{ 3. 對象變量 + 介面變量雙持有 }
procedure TForm1.btn3Click(Sender: TObject);
var
Obj: TTestObj;
Intf: ITest;
begin
Obj := TTestObj.Create; // Create
Intf := Obj; // 隱式轉換,_AddRef (RefCount=2)
Intf.DoWork; // DoWork
ShowMessage('**********');
// 過程結束:
// 1. Intf 離開作用域 → _Release (RefCount=1)
// 2. Obj 離開作用域 → 無自動釋放!洩漏風險
// 3. 程式結束時全局清理才釋放
end;
{ 4. 介面手動置 nil:強制提前釋放 }
procedure TForm1.btn4Click(Sender: TObject);
var
Intf: ITest;
begin
Intf := TTestObj.Create; // Create
Intf.DoWork; // DoWork
Intf := nil; // 關鍵:_Release → RefCount=0 → Destroy (立即)
ShowMessage('**********');
end;
end.
| 場景 | 輸出順序 | 關鍵點 |
|---|---|---|
| btn1 (對象+Free) | Create → DoWork → Destroy → ********** | 手動控制,釋放時機確定 |
| btn2 (純介面) | Create → DoWork → ********** → Destroy | 自動釋放在 過程結束後 (作用域出口) |
| btn3 (雙持有) | Create → DoWork → ********** → (程式結束才 Destroy) | 對象變量阻礙釋放,介面計數不歸零 |
| btn4 (介面置 nil) | Create → DoWork → Destroy → ********** | 手動控制釋放時機,同對象 Free |
var
Obj: TMyClass;
Intf: IMyInterface;
begin
Obj := TMyClass.Create;
Intf := Obj; // RefCount=2
// ...
end; // Intf 釋放 → RefCount=1,Obj 無自動釋放 → 洩漏
解決:
Obj := nil 斷開對象引用(但對象不會自動釋放,仍需 Free)。Intf := TMyClass.Create 直接賦值介面,不聲明對象變量。Self 賦給介面 → 過早釋放constructor TMyClass.Create;
var
Intf: IMyInterface;
begin
inherited;
Intf := Self; // 臨時介面變量,RefCount=1
// 做點事...
end; // Intf 離開作用域 → _Release → RefCount=0 → Destroy!對象在構造中被銷毀
解決:構造函數中絕不將 Self 賦給介面變量。若需構造時註冊到管理器,由外部調用方在構造完成後處理。
引用計數永不歸零,雙雙洩漏。
解決:
[weak] 屬性修飾符,Delphi 10.4+ 支援)。ReleaseReferences 方法主動置 nil。各模塊有獨立內存管理器,對象在 DLL 中創建,EXE 中釋放(或反之)會崩潰。
解決:
ShareMem 共享內存管理器(需作第一單元引用)。CreateXXX/ReleaseXXX,EXE 僅持有介面調用業務方法,釋放時調用 DLL 導出函數。var I: IMyIntf; I := TImpl.Create; 享受自動釋放。I := nil; 立即觸發 Destroy。ReportMemoryLeaksOnShutdown := True + FastMM4,關注 TInterfacedObject 衍生類。Delphi 介面釋放看似自動、實則有嚴格規則:引用計數歸零觸發 Destroy,作用域出口自動 _Release,nil 賦值立即釋放。核心避坑三條:**不混持對象/介面、不在構造函數賦 Self、防循環引用**。掌握這套機制,介面編程即安全又優雅,真正實現「資源獲取即初始化」(RAII) 的托管體驗。