熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Delphi編程 >> 正文

基於Delphi的接口編程入門

2022-06-13   來源: Delphi編程 
 為什麼使用接口?

  舉個例子好了有這樣一個賣票服務電影院可以賣票歌劇院可以賣票客運站也可以賣票那麼我們是否需要把電影院歌劇院和客運站都設計成一個類架構以提供賣票服務?要知道連經理人都可以賣票很顯然不適合把經理人也包括到賣票服務的繼承架構中我們需要的只是一個共通的賣票服務於是賣票的服務是個接口電影院歌劇院什麼的只要都遵循這樣一個服務定義就能很好地相互交互和溝通(如果須要的話)

  如何在Delphi中使用接口

  聲明接口

IMyInterface = interface(IInterface) //說明()
[{EDFBEBCBCCFDAEA}] //說明()
function GetName(const str: String): String; stdcall;

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; //說明()
function _AddRef: Integer; stdcall; //使接口引用數加
function _Release: Integer; stdcall;//使接口引用數減當小於等於時作釋放動作
end;

  說明()如果有繼續關系則在括號裡填父接口否則省卻IMyInterface = interface這樣就行

  說明()此GUID可選如果要實現具有COM特性的接口的話則需要加上Delphi中對於有GUID的接口在運行時在VMT表的預定位置生成接口的信息如接口方法的定義方法參數定義能詳細信息

  說明()接口必須實現這三個函數

  接口的實現

  接口服務是由類來實現的

TIntfClass = class(TObject IMyInterface)
private
 FCounter: Integer;
 FRefCount: Integer;
public
 function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
 
end;

  獲取接口

  a 使用類型轉換

var aIntf: IMyInterface;
begin
 aObj := TIntfClassCreate;
try
 aIntf := (IMyInterface(aObj);
 

  b 利用Delphi編譯器內建機制aIntf := aObj

  c 利用對象的QueryInterface方法如OleCheck(aObjQueryInterface(IID aIntf)); 只能存取有GUID的COM接口

  d 利用as操作符

  使用as操作符必須符合下面條件

  接口必須明確地指定是從IInterface接口繼承下來

  必須擁有GUID值

  在Delphi中接口的實現類還必須是從TInterfacedObject繼承下來才行

TIntfClass = class(TInterfacedObject IMyInterface)

  接口和對象生命期

  因為Delphi會自行檢查接口如果在使用後沒有釋放而在生成的程序裡加上釋放代碼但也因這樣帶來了問題如下面代碼

var
 i: Integer;
 aObj: TIntfClass;
 aIntf IMyInterface;
begin
 aObj := TIntfclassCreate;
 try
  aIntf := aObj;
  aIntfGetName
 finally
  aIntf := nil;
  FreeAndNil(aObj);
end;

  上面的代碼執行的話會產生存取違規錯誤是因為對接口置nil時已釋放接口而FreeAndNil(aObj)會再釋放aIntf一次而在對aIntf置
nil時已釋放了該對象解決這個問題只要不讓接口干擾對象的生命期就可以了在Release中只需減引用計數而不做釋放的動作

function TIntfClass_Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
end;

  接口的委托(Interface Delegation)

  分為兩種

   對象接口委托

   類對象委托

   對象接口委托假如已有下面接口定義

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 TImplClassQueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID Obj) then
 Result :=
else
 Result := E_NOINTERFACE;
end;

function TImplClass_Release: Integer;
begin
 Result := InterlockedDecrement(FRefCount);
if Result = 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 TIntfServiceClassCreate;
begin
 FImplService := TImplClassCreate;
end;

constructor TIntfServiceclassCreate(aClass: TClass);
var
 instance: TImplClass;
begin
 instance := TImplClass(aClassNewInstance);
 FImplService := instanceCreate;
end;

destructor TIntfServiceClassDestroy;
begin
 FImplService := nil; //遵照TImplClass使用引用計數來控制對象生命周期看TImplClass的Destroy實現
 inherited;
end;

  接口和RTTI

  Delphi中在VMT位移處定義了接口哥格指針vmtIntfTable =

  相關函數

GetInterfaceCount; //獲取接口數量
GetInterfaceTable; //獲取接口表格

  相關結構

TInterfaceEntry = packed record
IID: TGUID;
VTable: Pointer;
IOffset: Integer;
ImplGetter: Integer;
end;

PInterfaceTable = ^TInterfaceTable;
TInterfaceTable = packed record
EntryCount: Integer;
Entries: array[] of TInterfaceEntry;
end;

  Self是指向VMT指針的指針所以SelfGetInterfaceTableEntryCount等價於

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;


From:http://tw.wingwit.com/Article/program/Delphi/201311/24851.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.