| 1.1 為什麼使用接口?舉個例子好了:有這樣一個賣票服務,電影院可以賣票,歌劇院可以賣票,客運站也可以賣票,那麼我們是否需要把電影院、、歌劇院和客運站都設計成一個類架構以提供賣票服務?要知道,連經理人都可以賣票,很顯然不適合把經理人也包括到賣票服務的繼承架構中,我們需要的只是一個共通的賣票服務。於是,賣票的服務是個接口,電影院、歌劇院什麼的只要都遵循這樣一個服務定義就能很好地相互交互和溝通(如果須要的話)。 1.2 如何在Delphi中使用接口 | | 1.2.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這樣就行。說明(2):此GUID可選,如果要實現具有COM特性的接口的話則需要加上,Delphi中對於有GUID的接口在運行時在VMT表的預定位置生成接口的信息,如接口方法的定義、方法參數定義能詳細信息。說明(3):接口必須實現這三個函數。 1.2.2 接口的實現接口服務是由類來實現的。 TIntfClass = class(TObject, IMyInterface)PRivateFCounter: Integer;FRefCount: Integer;publicfunction QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;...end;1.2.3 獲取接口a. 使用類型轉換。 如:var aIntf: IMyInterface;beginaObj := TIntfClass.Create;tryaIntf := (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)1.2.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;1.2.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;implementationfunction 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;beginResult := InterlockedDecrement(FRefCount);if Result = 0 thenDestroy;end;... ...現在有另外一個類TIntfServiceClass要實現IImplInterface接口,不用重新定義,只須使用上面的TImplClass就可以:TIntfServiceClass = class(TObject, IImplInterface)privateFImplService: IImplInterface; //FSrvObj: TImplClass; //如果是用類對象委託的話publicConstructor 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;beginFImplService := TImplClass.Create;end;constructor TIntfServiceclass.Create(aClass: TClass);var instance: TImplClass;begininstance := TImplClass(aClass.NewInstance);FImplService := instance.Create ;end;destructor TIntfServiceClass.Destroy;beginFImplService := nil; //遵照TImplClass使用引用計數來控制對像生命週期,看TImplClass的Destroy實現。 inherited;end;1.2.6 接口和RTTIDelphi中在VMT-72位移處定義了接口哥格指針:vmtIntfTable = -72。相關函數:GetInterfaceCount; //獲取接口數量。 GetInterfaceTable; //獲取接口表格。相關結構:TInterfaceEntry = packed recordIID: TGUID;VTable: Pointer;IOffset: Integer;ImplGetter: Integer;end;PInterfaceTable = ^TInterfaceTable;TInterfaceTable = packed recordEntryCount: 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 = recordname: String; //接口名稱UnitName: String;//接口聲明的程序單元名稱MDA: TIntfMethEntryArray;//儲存接口中方法信息的動態數組IID: TGUID;//接口的GUID值Info: PTypeInfo;//描述接口信息的指針AncInfo: PTypeInfo ;//描述父代信息的指針NumAnc: Integer;//此接口繼承自父代接口的方法數目end;TIntfMethEntryArray的定義如下:typeTCallConv = (ccReg, ccCdecl, ccPascal, ccStdCall, ccSafeCall);TIntfMethEntry = recordName: String;//方法名稱CC: TCallConv;//調用慣例Pos: Integer;//方法在接口中的位置ParamCount: Integer;//方法的參數數目ResultInfo: PTypeInfo;//描述方法回傳類型的信息指針SelfInfo: PTypeInfo;//描述方法本身的信息指針Params: TIntfParamEntryArray;//描述參數信息的動態數組HasRTTI: Boolean;//這個方法是否擁有RTTI信息的布爾值end;TIntfMethEntryArray = array of TIntfMethEntry;參數信息TIntfParamEntry定義:TIntfParamEntry = recordFlags: TParamFlags;Name: String;Info: PTypeInfo;end;TTypeInfo = recordKind: TTypeKind;//數據類型Name: ShortString;//類型信息的字符串格式end; |
|