為什么使用接口?
舉個(gè)例子好了:有這樣一個(gè)賣票服務(wù),電*可以賣票,歌劇院可以賣票,客運(yùn)站也可以賣票,那么我們是否需要把電*、、歌劇院和客運(yùn)站都設(shè)計(jì)成一個(gè)類架構(gòu)以提供賣票服務(wù)?要知道,連經(jīng)理人都可以賣票,很顯然不適合把經(jīng)理人也包括到賣票服務(wù)的繼承架構(gòu)中,我們需要的只是一個(gè)共通的賣票服務(wù)。于是,賣票的服務(wù)是個(gè)接口,電*、歌劇院什么的只要都遵循這樣一個(gè)服務(wù)定義就能很好地相互交互和溝通(如果須要的話)。
如何在Delphi中使用接口
1、聲明接口
IMyInterface = interface(IInterface) //說(shuō)明(1)
[’{63E072DF-B81E-4734-B3CB-3C23C7FDA8EA}’] //說(shuō)明(2)
function GetName(const str: String): String; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; //說(shuō)明(3)
function _AddRef: Integer; stdcall; //使接口引用數(shù)加1。
function _Release: Integer; stdcall;//使接口引用數(shù)減1,當(dāng)小于等于0時(shí)作釋放動(dòng)作。
end;
說(shuō)明(1):如果有繼續(xù)關(guān)系則在括號(hào)里填父接口,否則省卻,如:IMyInterface = interface這樣就行。
說(shuō)明(2):此GUID可選,如果要實(shí)現(xiàn)具有COM特性的接口的話則需要加上,Delphi中對(duì)于有GUID的接口在運(yùn)行時(shí)在VMT表的預(yù)定位置生成接口的信息,如接口方法的定義、方法參數(shù)定義能詳細(xì)信息。
說(shuō)明(3):接口必須實(shí)現(xiàn)這三個(gè)函數(shù)。
2、接口的實(shí)現(xiàn)
接口服務(wù)是由類來(lái)實(shí)現(xiàn)的。
TIntfClass = class(TObject, IMyInterface)
private
FCounter: Integer;
FRefCount: Integer;
public
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
...
end;
3、獲取接口
a. 使用類型轉(zhuǎn)換。 如:
var aIntf: IMyInterface;
begin
aObj := TIntfClass.Create;
try
aIntf := (IMyInterface(aObj);
...
b. 利用Delphi編譯器內(nèi)建機(jī)制。 如:aIntf := aObj。
c. 利用對(duì)象的QueryInterface方法。如OleCheck(aObj.QueryInterface(IID, aIntf)); 只能存取有GUID的COM接口。
d. 利用as操作符。
使用as操作符必須符合下面條件:
1.接口必須明確地指定是從IInterface接口繼承下來(lái)。
2.必須擁有GUID值
在Delphi7中接口的實(shí)現(xiàn)類還必須是從TInterfacedObject繼承下來(lái)才行,如:
TIntfClass = class(TInterfacedObject, IMyInterface)
4、接口和對(duì)象生命期
因?yàn)镈elphi會(huì)自行檢查接口如果在使用后沒(méi)有釋放而在生成的程序里加上釋放代碼,但也因這樣帶來(lái)了問(wèn)題,如下面代碼:
var
i: Integer;
aObj: TIntfClass;
aIntf: IMyInterface;
begin
aObj := TIntfclass.Create;
try
aIntf := aObj;
aIntf.GetName...
finally
aIntf := nil;
FreeAndNil(aObj);
end;
上面的代碼執(zhí)行的話會(huì)產(chǎn)生存取違規(guī)錯(cuò)誤,是因?yàn)閷?duì)接口置nil時(shí)已釋放接口,而FreeAndNil(aObj)會(huì)再釋放aIntf一次,而在對(duì)aIntf置
nil時(shí)已釋放了該對(duì)象。解決這個(gè)問(wèn)題只要不讓接口干擾對(duì)象的生命期就可以了,在Release中只需減引用計(jì)數(shù)而不做釋放的動(dòng)作。
function TIntfClass._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
end; 5、接口的委托(Interface Delegation)
分為兩種:
1. 對(duì)象接口委托
2. 類對(duì)象委托。
. 對(duì)象接口委托,假如已有下面接口定義:
IImplInterface = interface(IInterface)
function ConvertToUSD(const iNTD: Integer): Double;
function ConvertToRMB(const iNTD: Integer): Double;
end;
接著有一個(gè)類實(shí)現(xiàn)了該接口:
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;
... ...
現(xiàn)在有另外一個(gè)類TIntfServiceClass要實(shí)現(xiàn)IImplInterface接口,不用重新定義,只須使用上面的TImplClass就可以:
TIntfServiceClass = class(TObject, IImplInterface)
private
FImplService: IImplInterface;
//FSrvObj: TImplClass; //如果是用類對(duì)象委托的話
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; //如果是用對(duì)象委托的話。
end;
實(shí)現(xiàn)如下:
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使用引用計(jì)數(shù)來(lái)控制對(duì)象生命周期,看TImplClass的Destroy實(shí)現(xiàn)。
inherited;
end;
6、接口和RTTI
Delphi中在VMT-72位移處定義了接口哥格指針:vmtIntfTable = -72。
相關(guān)函數(shù):
GetInterfaceCount; //獲取接口數(shù)量。
GetInterfaceTable; //獲取接口表格。
相關(guān)結(jié)構(gòu):
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等價(jià)于:
aPtr := PPointer(Integeer((Pointer(Self))^) + vmtIntfTable)^;
只要在聲明中使用M+/M-指令就能在Delphi中編譯出的程序里添加RTTI信息,如:
{$M+}
iInvokable = interface(IInterface)
{$M-}
接口的RTTI信息由TIntfMetaData記錄結(jié)構(gòu)定義:
TIntfMetaData = record
name: String; //接口名稱
UnitName: String; //接口聲明的程序單元名稱
MDA: TIntfMethEntryArray; //儲(chǔ)存接口中方法信息的動(dòng)態(tài)數(shù)組
IID: TGUID; //接口的GUID值
Info: PTypeInfo; //描述接口信息的指針
AncInfo: PTypeInfo; //描述父代信息的指針
NumAnc: Integer; //此接口繼承自父代接口的方法數(shù)目
end;
TIntfMethEntryArray的定義如下:
type
TCallConv = (ccReg, ccCdecl, ccPascal, ccStdCall, ccSafeCall);
TIntfMethEntry = record
Name: String; //方法名稱
CC: TCallConv; //調(diào)用慣例
Pos: Integer; //方法在接口中的位置
ParamCount: Integer; //方法的參數(shù)數(shù)目
ResultInfo: PTypeInfo; //描述方法回傳類型的信息指針
SelfInfo: PTypeInfo; //描述方法本身的信息指針
Params: TIntfParamEntryArray; //描述參數(shù)信息的動(dòng)態(tài)數(shù)組
HasRTTI: Boolean; //這個(gè)方法是否擁有RTTI信息的布爾值
end;
TIntfMethEntryArray = array of TIntfMethEntry;
參數(shù)信息TIntfParamEntry定義:
TIntfParamEntry = record
Flags: TParamFlags;
Name: String;
Info: PTypeInfo;
end;
TTypeInfo = record
Kind: TTypeKind; //數(shù)據(jù)類型
Name: ShortString; //類型信息的字符串格式
end;
舉個(gè)例子好了:有這樣一個(gè)賣票服務(wù),電*可以賣票,歌劇院可以賣票,客運(yùn)站也可以賣票,那么我們是否需要把電*、、歌劇院和客運(yùn)站都設(shè)計(jì)成一個(gè)類架構(gòu)以提供賣票服務(wù)?要知道,連經(jīng)理人都可以賣票,很顯然不適合把經(jīng)理人也包括到賣票服務(wù)的繼承架構(gòu)中,我們需要的只是一個(gè)共通的賣票服務(wù)。于是,賣票的服務(wù)是個(gè)接口,電*、歌劇院什么的只要都遵循這樣一個(gè)服務(wù)定義就能很好地相互交互和溝通(如果須要的話)。
如何在Delphi中使用接口
1、聲明接口
IMyInterface = interface(IInterface) //說(shuō)明(1)
[’{63E072DF-B81E-4734-B3CB-3C23C7FDA8EA}’] //說(shuō)明(2)
function GetName(const str: String): String; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; //說(shuō)明(3)
function _AddRef: Integer; stdcall; //使接口引用數(shù)加1。
function _Release: Integer; stdcall;//使接口引用數(shù)減1,當(dāng)小于等于0時(shí)作釋放動(dòng)作。
end;
說(shuō)明(1):如果有繼續(xù)關(guān)系則在括號(hào)里填父接口,否則省卻,如:IMyInterface = interface這樣就行。
說(shuō)明(2):此GUID可選,如果要實(shí)現(xiàn)具有COM特性的接口的話則需要加上,Delphi中對(duì)于有GUID的接口在運(yùn)行時(shí)在VMT表的預(yù)定位置生成接口的信息,如接口方法的定義、方法參數(shù)定義能詳細(xì)信息。
說(shuō)明(3):接口必須實(shí)現(xiàn)這三個(gè)函數(shù)。
2、接口的實(shí)現(xiàn)
接口服務(wù)是由類來(lái)實(shí)現(xiàn)的。
TIntfClass = class(TObject, IMyInterface)
private
FCounter: Integer;
FRefCount: Integer;
public
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
...
end;
3、獲取接口
a. 使用類型轉(zhuǎn)換。 如:
var aIntf: IMyInterface;
begin
aObj := TIntfClass.Create;
try
aIntf := (IMyInterface(aObj);
...
b. 利用Delphi編譯器內(nèi)建機(jī)制。 如:aIntf := aObj。
c. 利用對(duì)象的QueryInterface方法。如OleCheck(aObj.QueryInterface(IID, aIntf)); 只能存取有GUID的COM接口。
d. 利用as操作符。
使用as操作符必須符合下面條件:
1.接口必須明確地指定是從IInterface接口繼承下來(lái)。
2.必須擁有GUID值
在Delphi7中接口的實(shí)現(xiàn)類還必須是從TInterfacedObject繼承下來(lái)才行,如:
TIntfClass = class(TInterfacedObject, IMyInterface)
4、接口和對(duì)象生命期
因?yàn)镈elphi會(huì)自行檢查接口如果在使用后沒(méi)有釋放而在生成的程序里加上釋放代碼,但也因這樣帶來(lái)了問(wèn)題,如下面代碼:
var
i: Integer;
aObj: TIntfClass;
aIntf: IMyInterface;
begin
aObj := TIntfclass.Create;
try
aIntf := aObj;
aIntf.GetName...
finally
aIntf := nil;
FreeAndNil(aObj);
end;
上面的代碼執(zhí)行的話會(huì)產(chǎn)生存取違規(guī)錯(cuò)誤,是因?yàn)閷?duì)接口置nil時(shí)已釋放接口,而FreeAndNil(aObj)會(huì)再釋放aIntf一次,而在對(duì)aIntf置
nil時(shí)已釋放了該對(duì)象。解決這個(gè)問(wèn)題只要不讓接口干擾對(duì)象的生命期就可以了,在Release中只需減引用計(jì)數(shù)而不做釋放的動(dòng)作。
function TIntfClass._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
end; 5、接口的委托(Interface Delegation)
分為兩種:
1. 對(duì)象接口委托
2. 類對(duì)象委托。
. 對(duì)象接口委托,假如已有下面接口定義:
IImplInterface = interface(IInterface)
function ConvertToUSD(const iNTD: Integer): Double;
function ConvertToRMB(const iNTD: Integer): Double;
end;
接著有一個(gè)類實(shí)現(xiàn)了該接口:
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;
... ...
現(xiàn)在有另外一個(gè)類TIntfServiceClass要實(shí)現(xiàn)IImplInterface接口,不用重新定義,只須使用上面的TImplClass就可以:
TIntfServiceClass = class(TObject, IImplInterface)
private
FImplService: IImplInterface;
//FSrvObj: TImplClass; //如果是用類對(duì)象委托的話
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; //如果是用對(duì)象委托的話。
end;
實(shí)現(xiàn)如下:
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使用引用計(jì)數(shù)來(lái)控制對(duì)象生命周期,看TImplClass的Destroy實(shí)現(xiàn)。
inherited;
end;
6、接口和RTTI
Delphi中在VMT-72位移處定義了接口哥格指針:vmtIntfTable = -72。
相關(guān)函數(shù):
GetInterfaceCount; //獲取接口數(shù)量。
GetInterfaceTable; //獲取接口表格。
相關(guān)結(jié)構(gòu):
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等價(jià)于:
aPtr := PPointer(Integeer((Pointer(Self))^) + vmtIntfTable)^;
只要在聲明中使用M+/M-指令就能在Delphi中編譯出的程序里添加RTTI信息,如:
{$M+}
iInvokable = interface(IInterface)
{$M-}
接口的RTTI信息由TIntfMetaData記錄結(jié)構(gòu)定義:
TIntfMetaData = record
name: String; //接口名稱
UnitName: String; //接口聲明的程序單元名稱
MDA: TIntfMethEntryArray; //儲(chǔ)存接口中方法信息的動(dòng)態(tài)數(shù)組
IID: TGUID; //接口的GUID值
Info: PTypeInfo; //描述接口信息的指針
AncInfo: PTypeInfo; //描述父代信息的指針
NumAnc: Integer; //此接口繼承自父代接口的方法數(shù)目
end;
TIntfMethEntryArray的定義如下:
type
TCallConv = (ccReg, ccCdecl, ccPascal, ccStdCall, ccSafeCall);
TIntfMethEntry = record
Name: String; //方法名稱
CC: TCallConv; //調(diào)用慣例
Pos: Integer; //方法在接口中的位置
ParamCount: Integer; //方法的參數(shù)數(shù)目
ResultInfo: PTypeInfo; //描述方法回傳類型的信息指針
SelfInfo: PTypeInfo; //描述方法本身的信息指針
Params: TIntfParamEntryArray; //描述參數(shù)信息的動(dòng)態(tài)數(shù)組
HasRTTI: Boolean; //這個(gè)方法是否擁有RTTI信息的布爾值
end;
TIntfMethEntryArray = array of TIntfMethEntry;
參數(shù)信息TIntfParamEntry定義:
TIntfParamEntry = record
Flags: TParamFlags;
Name: String;
Info: PTypeInfo;
end;
TTypeInfo = record
Kind: TTypeKind; //數(shù)據(jù)類型
Name: ShortString; //類型信息的字符串格式
end;