Delphi下的接口編程學習筆記

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)

private

FCounter: Integer;

FRefCount: Integer;

public

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

...

end;

1.2.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)

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;

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;

1.2.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;

 
Delphi下的接口編程學習筆記(原創)
 Delphi下的接口編程  Delphi下的接口編程學習筆記1.1 爲什麽使用接口? 舉個例子好了:有這樣一個賣票服務,電影院可以賣票,歌劇院可以賣票,客運站也可以賣票,那麽我們是否需要把電影院、、歌劇院和客運站...查看完整版>>Delphi下的接口編程學習筆記(原創)
 
學習"圖解路由器接口及連接"筆記
非常感謝"圖解路由器接口及連接"的作者,雖然我不知道誰! 需原文網友請發郵件至:duan.hongwei&gmail.com注用@替換&一、AUI接口這個接口在客戶那裏見過,連接著一根粗纜。當時不是很明白。看了文章之...查看完整版>>學習"圖解路由器接口及連接"筆記
 
Java 網絡編程---I/O部分學習筆記整理1
網絡程序的很大一部分是簡單的輸入輸出,即從一個系統向另一個系統移動字節。字節就是字節,在很大程度上,讀服務器發送的數據與讀取文件沒什麽不同;向客戶傳送數據與寫入一個文件也沒有什麽區別。 Java中輸入...查看完整版>>Java 網絡編程---I/O部分學習筆記整理1
 
Java網絡編程的學習筆記(二)
  第二部分 用URL檢索數據一.URL類Java程序定位和檢索網絡上的數據最簡單的方法是使用URL類。Java.net.URL類是對統一資源定位符的抽象。URL對象建立後,它的字段就不再改變。構造java.net.URL實例的六個構造器:1...查看完整版>>Java網絡編程的學習筆記(二)
 
Java網絡編程的學習筆記(一)
  第一部分 查找Internet地址 Java.net.InetAddress類是Java的IP地址封裝類。 一.InetAddress有適合于初始化InetAddress對象的3個靜態方法,它們是:1)public static InetAddress InetAddress.getByName(String h...查看完整版>>Java網絡編程的學習筆記(一)