zoukankan      html  css  js  c++  java
  • 基于Delphi的接口编程入门

    基于Delphi的接口编程入门

    为什么使用接口?

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

      如何在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;

  • 相关阅读:
    最大子数组求和并进行条件组合覆盖测试
    Ubuntu 16.04 c++ Google框架单元测试
    The directory '/home/stone/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If execu
    Problem executing scripts APT::Update::Post-Invoke-Success 'if /usr/bin/test -w /var/cache/app-info -a -e /usr/bin/appstreamcli; then appstreamcli refresh > /dev/null; fi'
    个人博客作业三:微软小娜APP的案例分析
    补交 作业一
    补交 作业二:个人博客作业内容:需求分析
    嵌入式软件设计第12次实验报告
    嵌入式软件设计第11次实验报告
    嵌入式软件设计第10次实验报告
  • 原文地址:https://www.cnblogs.com/karkash/p/3086113.html
Copyright © 2011-2022 走看看