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

Delphi 介面程式設計入門:IInterface、GUID、引用計數、委派與 RTTI 完整指南

     編輯:Delphi

介面是 Delphi 實現「契約導向設計」、「多型解耦」、「COM 互操作」的核心機制。本文系統梳理介面宣告、實現、獲取、生命週期管理、委派模式與 RTTI 反射,並對比類別繼承差異,附完整代碼範例。

為什麼用介面?

「電影院、歌劇院、客運站、經理人都能賣票,但不適合共用繼承體系。定義 ITicketSeller 介面,誰實現誰就能賣票,調用者只依賴介面不依賴具體類別。」

1. 介面宣告


type
  IMyInterface = interface(IInterface) // 或 IUnknown
    ['{63E072DF-B81E-4734-B3CB-3C23C7FDA8EA}'] // GUID,COM 互操作必須
    function GetName(const S: String): String; stdcall;
    // IInterface 三大核心方法 (若繼承 IInterface 編譯器自動實現)
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;
  • GUID:Ctrl+Shift+G 產生。有 GUID 才能用 Supports、as 轉型、COM 封送。
  • stdcall:COM 標準呼叫慣例,跨語言/進程必須。

2. 介面實現類別


type
  TIntfClass = class(TInterfacedObject, IMyInterface) // Delphi 7+ 建議繼承 TInterfacedObject
  private
    FCounter: Integer;
  public
    function GetName(const S: String): String; stdcall;
    // QueryInterface/_AddRef/_Release 由 TInterfacedObject 實現
  end;

function TIntfClass.GetName(const S: String): String;
begin
  Result := 'Hello ' + S + ' from ' + ClassName;
end;

3. 獲取介面引用 (四種方式)

方式代碼條件
硬轉型Intf := IMyInterface(Obj)Obj 必須實現該介面,否則編譯錯誤
隱含轉型Intf := Obj編譯器自動生成 QueryInterface 呼叫
QueryInterfaceOleCheck(Obj.QueryInterface(IID_IMyInterface, Intf))需 GUID,僅限 COM 介面
as 操作符Intf := Obj as IMyInterface需繼承 IInterface 且有 GUID,失敗拋 EInvalidCast

4. 生命週期與引用計數陷阱


var Obj: TIntfClass; Intf: IMyInterface;
begin
  Obj := TIntfClass.Create;
  try
    Intf := Obj;        // _AddRef → RefCount=1
    Intf.GetName('A');
  finally
    Intf := nil;        // _Release → RefCount=0 → Obj 自動 Free
    FreeAndNil(Obj);    // 雙重 Free → AV 存取違規!
  end;
end;

解決方案:在 _Release 中僅遞減計數,不釋放物件,由外部統一管理物件壽命:


function TIntfClass._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  // 不要在這裡呼叫 Destroy/Free
end;

或改用 TInterfacedObject 預設行為:介面釋放即釋放物件,不要 再手動 Free 物件。

5. 介面委派 — 組合優於繼承


// 既有實現
type
  IImpl = interface(IInterface)
    ['{...}']
    function ConvertToUSD(const NTD: Integer): Double; stdcall;
  end;
  TImpl = class(TInterfacedObject, IImpl)
    function ConvertToUSD(const NTD: Integer): Double; stdcall;
  end;

// 新服務類別委派給 TImpl
type
  TService = class(TInterfacedObject, IImpl)
  private
    FImpl: IImpl;
  public
    constructor Create; overload;
    constructor Create(AClass: TClass); overload;
    destructor Destroy; override;
    property Impl: IImpl read FImpl implements IImpl; // 關鍵字 implements
  end;

constructor TService.Create;
begin
  FImpl := TImpl.Create;
end;

constructor TService.Create(AClass: TClass);
var Inst: TImpl;
begin
  Inst := TImpl(AClass.NewInstance);
  FImpl := Inst.Create;
end;

destructor TService.Destroy;
begin
  FImpl := nil; // 釋放介面引用
  inherited;
end;
  • implements 關鍵字讓編譯器自動生成介面方法轉發代碼。
  • 可動態切換實現類別 (策略模式)、多介面委派給不同內部物件。

6. 介面 RTTI (Run-Time Type Information)

Delphi 在 VMT 偏移 -72 (vmtIntfTable) 存放介面表。啟用 {$M+} 產生介面 RTTI。


{$M+}
IInvokable = interface(IInterface)
{$M-}

核心結構


type
  TInterfaceEntry = packed record
    IID: TGUID;           // 介面 GUID
    VTable: Pointer;      // 虛函數表指標
    IOffset: Integer;     // 實現類別偏移
    ImplGetter: Integer;  // 獲取實現器編碼
  end;
  TInterfaceTable = packed record
    EntryCount: Integer;
    Entries: array[0..9999] of TInterfaceEntry;
  end;

常用函數


GetInterfaceCount;        // 介面數量
GetInterfaceTable;        // 介面表指標
// 範例:列印類別實現的所有介面 GUID
var Table: PInterfaceTable; I: Integer;
begin
  Table := PPointer(Integer(PPointer(Self)^) + vmtIntfTable)^;
  for I := 0 to Table.EntryCount - 1 do
    Memo1.Lines.Add(GUIDToString(Table.Entries[I].IID));
end;

方法/參數 RTTI

TIntfMethEntryArray 包含方法名、呼叫慣例、參數個數、回傳類型、參數旗標/名稱/類型等完整元資料,支援動態呼叫、序列化、Web Service 發布等進階場景。

總結:介面 vs 類別繼承

特性介面類別繼承
多重實現單類別可實現多介面單繼承
契約與實現分離徹底耦合
跨語言/進程支援 (COM)不支援
引用計數管理自動手動
預設方法實現無 (Delphi 10.4+ 支援 default)有
適用場景插件架構、服務契約、COM、解耦IS-A 關係、共享實現

善用介面,能讓 Delphi 程式架構更靈活、可測試、可擴展,是邁向現代軟體工程的關鍵一步。

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