一为什么使用接口? 举个例子好了有这样一个卖票服务电影院可以卖票歌剧院可以卖票客运站也可以卖票那么我们是否需要把电影院歌剧院和客运站都设计成一个类架构以提供卖票服务?要知道连经理人都可以卖票很显然不适合把经理人也包括到卖票服务的继承架构中我们需要的只是一个共通的卖票服务于是卖票的服务是个接口电影院歌剧院什么的只要都遵循这样一个服务定义就能很好地相互交互和沟通(如果须要的话)
二如何在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;