標籤:編譯器 function ack facet 執行 ram and pos begin
為什麼使用介面?
舉個例子好了:有這樣一個賣票服務,電影院可以賣票,歌劇院可以賣票,客運站也可以賣票,那麼我們是否需要把電影院、、歌劇院和客運站都設計成一個類架構以提供賣票服務?要知道,連經理人都可以賣票,很顯然不適合把經理人也包括到賣票服務的繼承架構中,我們需要的只是一個共通的賣票服務。於是,賣票的服務是個介面,電影院、歌劇院什麼的只要都遵循這樣一個服務定義就能很好地相互互動和溝通(如果需要的話)。
如何在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這樣就行。
說明(2):此GUID可選,如果要實現具有COM特性的介面的話則需要加上,Delphi中對於有GUID的介面在運行時在VMT表的預定位置產生介面的資訊,如介面方法的定義、方法參數定義的詳細資料。
說明(3):介面必須實現這三個函數。
2、介面的實現
介面服務是由類來實現的。
TIntfClass = class(TObject, IMyInterface)
private
FCounter: Integer;
FRefCount: Integer;
public
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;
基於Delphi的介面編程入門