基于Delphi的接口程式設計入門
為什麼使用接口?
舉個例子好了:有這樣一個賣票服務,電影院可以賣票,歌劇院可以賣票,客運站也可以賣票,那麼我們是否需要把電影院、、歌劇院和客運站都設計成一個類架構以提供賣票服務?要知道,連經理人都可以賣票,很顯然不适合把經理人也包括到賣票服務的繼承架構中,我們需要的隻是一個共通的賣票服務。于是,賣票的服務是個接口,電影院、歌劇院什麼的隻要都遵循這樣一個服務定義就能很好地互相互動和溝通(如果須要的話)。
如何在Delphi中使用接口
1、聲明接口
IMyInterface = interface(IInterface) //說明(1) ['{63E072DF-B81E-4734-B3CB-3C23C7FDA8EA}'] //說明(2) function GetName(const str: String): String; stdcall; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; //說明(3) function _AddRef: Integer; stdcall; //使接口引用數加1。 function _Release: Integer; stdcall;//使接口引用數減1,當小于等于0時作釋放動作。 end; |
說明(1):如果有繼承關系則在括号裡填父接口,否則省卻,如:IMyInterface = interface這樣就行。
type
IInterface = interface
['{00000000-0000-0000-C000-000000000046}']
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
說明(2):此GUID(全球唯一辨別符)可選,如果要實作具有COM特性的接口的話則需要加上,Delphi中對于有GUID的接口在運作時在VMT表的預定位置生成接口的資訊,如接口方法的定義、方法參數定義能詳細資訊。
說明(3):接口必須實作這三個函數。
2、接口的實作
接口服務是由類來實作的。
TIntfClass = class(TObject, //繼承唯一的一個T打頭的父類 IMyInterface) //可繼承實作多個I打頭的接口 private FCounter: Integer; FRefCount: Integer; public // IMyInterface接口方法的實作 function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; ... end; |
3、擷取接口
a. 使用類型轉換。 如:
var aIntf: IMyInterface; begin aObj := TIntfClass.Create; try aIntf := (IMyInterface(aObj); ... |
b. 利用Delphi編譯器内建機制。 如:aIntf := aObj。
c. 利用對象的QueryInterface方法。如OleCheck(aObj.QueryInterface(IID, aIntf)); 隻能存取有GUID的COM接口。
d. 利用as操作符。
使用as操作符必須符合下面條件:
1.接口必須明确地指定是從IInterface接口繼承下來。
2.必須擁有GUID值
在Delphi7中接口的實作類還必須是從TInterfacedObject繼承下來才行,如:
TIntfClass = class(TInterfacedObject, IMyInterface) |
4、接口和對象生命期
因為Delphi會自行檢查接口如果在使用後沒有釋放而在生成的程式裡加上釋放代碼,但也因這樣帶來了問題,如下面代碼:
var i: Integer; aObj: TIntfClass; aIntf: IMyInterface; begin aObj := TIntfclass.Create; try aIntf := aObj; aIntf.GetName... finally aIntf := nil; FreeAndNil(aObj); end; |
上面的代碼執行的話會産生存取違規錯誤,是因為對接口置nil時已釋放接口,而FreeAndNil(aObj)會再釋放aIntf一次,而在對aIntf置
nil時已釋放了該對象。解決這個問題隻要不讓接口幹擾對象的生命期就可以了,在Release中隻需減引用計數而不做釋放的動作。
function TIntfClass._Release: Integer; begin Result := InterlockedDecrement(FRefCount); end; |
5、接口的委托(Interface Delegation)
分為兩種:
1. 對象接口委托
2. 類對象委托。
. 對象接口委托,假如已有下面接口定義:
IImplInterface = interface(IInterface) function ConvertToUSD(const iNTD: Integer): Double; function ConvertToRMB(const iNTD: Integer): Double; end; |
接着有一個類實作了該接口:
TImplClass = class(TObject, IImplInterface) private FRefCount: Integer; public function ConvertToUSD(const iNTD: Integer): Double; ... end; implementation function TImplClass.QueryInterface(const IID: TGUID; out Obj): HResult; begin if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; function TImplClass._Release: Integer; begin Result := InterlockedDecrement(FRefCount); if Result = 0 then Destroy; end; ... ... |
現在有另外一個類TIntfServiceClass要實作IImplInterface接口,不用重新定義,隻須使用上面的TImplClass就可以:
TIntfServiceClass = class(TObject, IImplInterface) private FImplService: IImplInterface; //FSrvObj: TImplClass; //如果是用類對象委托的話 public Constructor Create; overload; Destructor Destroy; override; Constructor Create(aClass: TClass); overload; property MyService: IImplInterface read FImplService implements IImplInterface; // property MyService: TImplClass read FSrvObj implements IImplInterface; //如果是用對象委托的話。 end; |
實作如下:
constructor TIntfServiceClass.Create; begin FImplService := TImplClass.Create; end; constructor TIntfServiceclass.Create(aClass: TClass); var instance: TImplClass; begin instance := TImplClass(aClass.NewInstance); FImplService := instance.Create; end; destructor TIntfServiceClass.Destroy; begin FImplService := nil; //遵照TImplClass使用引用計數來控制對象生命周期,看TImplClass的Destroy實作。 inherited; end; |
6、接口和RTTI
Delphi中在VMT-72位移處定義了接口哥格指針:vmtIntfTable = -72。
相關函數:
GetInterfaceCount; //擷取接口數量。 GetInterfaceTable; //擷取接口表格。 |
相關結構:
TInterfaceEntry = packed record IID: TGUID; VTable: Pointer; IOffset: Integer; ImplGetter: Integer; end; PInterfaceTable = ^TInterfaceTable; TInterfaceTable = packed record EntryCount: Integer; Entries: array[0..9999] of TInterfaceEntry; end; |
Self是指向VMT指針的指針,是以:Self.GetInterfaceTable.EntryCount等價于:
aPtr := PPointer(Integeer((Pointer(Self))^) + vmtIntfTable)^; |
隻要在聲明中使用M+/M-指令就能在Delphi中編譯出的程式裡添加RTTI資訊,如:
{$M+} iInvokable = interface(IInterface) {$M-} |
接口的RTTI資訊由TIntfMetaData記錄結構定義:
TIntfMetaData = record name: String; //接口名稱 UnitName: String; //接口聲明的程式單元名稱 MDA: TIntfMethEntryArray; //儲存接口中方法資訊的動态數組 IID: TGUID; //接口的GUID值 Info: PTypeInfo; //描述接口資訊的指針 AncInfo: PTypeInfo; //描述父代資訊的指針 NumAnc: Integer; //此接口繼承自父代接口的方法數目 end; |
TIntfMethEntryArray的定義如下:
type TCallConv = (ccReg, ccCdecl, ccPascal, ccStdCall, ccSafeCall); TIntfMethEntry = record Name: String; //方法名稱 CC: TCallConv; //調用慣例 Pos: Integer; //方法在接口中的位置 ParamCount: Integer; //方法的參數數目 ResultInfo: PTypeInfo; //描述方法回傳類型的資訊指針 SelfInfo: PTypeInfo; //描述方法本身的資訊指針 Params: TIntfParamEntryArray; //描述參數資訊的動态數組 HasRTTI: Boolean; //這個方法是否擁有RTTI資訊的布爾值 end; TIntfMethEntryArray = array of TIntfMethEntry; |
參數資訊TIntfParamEntry定義:
TIntfParamEntry = record Flags: TParamFlags; Name: String; Info: PTypeInfo; end; TTypeInfo = record Kind: TTypeKind; //資料類型 Name: ShortString; //類型資訊的字元串格式 end; |