其他语言

位置:IT落伍者 >> 其他语言 >> 浏览文章

基于Delphi的接口编程入门


发布日期:2022年08月29日
 
基于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;

               

上一篇:Delphi的结构体,共用体和位域的等效实现

下一篇:Delphi exe实例之间传递cmd参数