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

Delphi 介面編程 (3):介面釋放機制深度解析——自動、手動、陷阱

     編輯:Delphi

核心機制:引用計數驅動的自動釋放

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

關鍵陷阱與應對策略

陷阱 1:對象變量與介面變量同時持有 → 洩漏

var
  Obj: TMyClass;
  Intf: IMyInterface;
begin
  Obj := TMyClass.Create;
  Intf := Obj; // RefCount=2
  // ...
end; // Intf 釋放 → RefCount=1,Obj 無自動釋放 → 洩漏

解決:

  • 「要麼全用介面,要麼全用對象」,嚴禁混持。
  • 若必須轉換,用完立即 Obj := nil 斷開對象引用(但對象不會自動釋放,仍需 Free)。
  • 最佳:Intf := TMyClass.Create 直接賦值介面,不聲明對象變量。

陷阱 2:構造函數中將 Self 賦給介面 → 過早釋放

constructor TMyClass.Create;
var
  Intf: IMyInterface;
begin
  inherited;
  Intf := Self; // 臨時介面變量,RefCount=1
  // 做點事...
end; // Intf 離開作用域 → _Release → RefCount=0 → Destroy!對象在構造中被銷毀

解決:構造函數中絕不將 Self 賦給介面變量。若需構造時註冊到管理器,由外部調用方在構造完成後處理。

陷阱 3:循環引用(A 介面引用 B,B 介面引用 A)

引用計數永不歸零,雙雙洩漏。

解決:

  • 單向引用 + 弱引用模式(用 [weak] 屬性修飾符,Delphi 10.4+ 支援)。
  • 手動打斷:提供 ReleaseReferences 方法主動置 nil。
  • 設計階段避免雙向介面引用,改用事件/回調/觀察者模式。

陷阱 4:跨 DLL 介面傳遞導致雙重釋放

各模塊有獨立內存管理器,對象在 DLL 中創建,EXE 中釋放(或反之)會崩潰。

解決:

  • 使用 ShareMem 共享內存管理器(需作第一單元引用)。
  • 或約定「誰創建誰釋放」:DLL 導出 CreateXXX/ReleaseXXX,EXE 僅持有介面調用業務方法,釋放時調用 DLL 導出函數。

最佳實踐清單

  1. 默認用介面變量:var I: IMyIntf; I := TImpl.Create; 享受自動釋放。
  2. 需要確定釋放時機:I := nil; 立即觸發 Destroy。
  3. 需與非介面代碼交互:在邊界轉換,內部統一介面。
  4. 調試洩漏:ReportMemoryLeaksOnShutdown := True + FastMM4,關注 TInterfacedObject 衍生類。
  5. 文檔約定:函數返回介面 → 調用方無需釋放;返回對象 → 文檔註明所有權歸屬。

總結

Delphi 介面釋放看似自動、實則有嚴格規則:引用計數歸零觸發 Destroy,作用域出口自動 _Release,nil 賦值立即釋放。核心避坑三條:**不混持對象/介面、不在構造函數賦 Self、防循環引用**。掌握這套機制,介面編程即安全又優雅,真正實現「資源獲取即初始化」(RAII) 的托管體驗。

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