用 Lazarus 开发 OPC Client 3 (关于接口/Interface)

Delphi/Lazarus 里面有个概念就是接口,这个概念在很多语言里面也都存在,而且发扬,dot Net 就是这样。

提到接口自然而然就想到COM、DCOM、OLE、ActiveX等相关知识,不错,确实可以应用于这些环境,而且Delphi/Lazarus中的Interface功能强大到不仅实现微软基于COM的技术,在Linux或其他系统下Interface的设计理念和方法一样可用。这里我们仅仅记录一下在Windows下的心得。

Delphi/Lazarus下面向对象设计中多态、继承、封装利用Interface的方法和Class的方法一般会有所区别,很早有本书中就提到“在设计初期就必须明确,用接口开发,还是Class开发”。那么接口到底是什么呢?在高级语言中我们可能会比较模糊,与纯抽象类有毛线区别啊,都是“方法定义嘛”!这个就要从其内部结构说说了。

其实Interface在计算机内存中就是一片连续的方法说明,整个很关键哦,也就是为什么接口定义了,就不可以再随意更改了。必须重新定义新的接口,就算加一个功能也是如此。尤其是多个程序、多中语言进行沟通更是如此。按这个逻辑推导方法的顺序也不可以随意调整哦。

Delphi/Lazarus中接口很多我们大多会继承自IUnknown /IInterface,对它们是等价的。他们其中的3个方法也是COM的基础。

该代码是 FPC 下的,所以定义了很多条件编译开关

IUnknown = interface
  ['{00000000-0000-0000-C000-000000000046}']
  function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;                           out obj) : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  function _AddRef : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  function _Release : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
end;
IInterface = IUnknown;

而我们在使用中也往往会继承 TInterfacedObject,作为实现的父类,它毕竟帮我们实现了最基本的释放功能。也就是引用记数到0就释放资源。这样问题也就来了。也就是实现了资源的自动管理,这和dotNet不是一样吗,当然不同了,dotNet毕竟是后起之秀,晚好多年咧,都是安德森设计的所以资源控制上更厉害,但万事万物都有其两面性,我有时就是不想让它自动释放!“明明是智能释放哪里会有这样的情况发生为什么不用呢,记数到0就该释放”要解释这个情况不能从简单Class来解释,需要用比较复查的结构来描叙。

Type
  TGroup=class;

  IGroup=interface(IUnknown)
      procedure K1;
  end;

  IClient=interface(IUnknown)
      procedure K2;
  end;

  TClient=class(TInterfacedObject,IClient)
     public FGroup:IGroup;
       procedure K1;
  end;

  TGroup=class(TInterfacedObject,IGroup)
    public  FClient:IClient;
    procedure K2;
  end;

var
  _ClientObj:TClient;
  _GroupObj:TGroup;

  _Client_IF:IClient;
  _Group_IF:IGroup;
. . .
begin
  _ClientObj:=TClient.Create;
  _GroupObj:=TGroup.Create;
  _ClientObj.FGroup:=_GroupObj;
  _GroupObj.FClient:=_ClientObj;

  _Client_IF:=_ClientObj;
  _Group_IF:=_GroupObj;

  _Client_IF:=nil;  // OK
  _Group_IF:=nil;  // Error
end.

(dotNet也是自动释放,后面章节也会提到如何使用dotNet而不自动释放),在Delphi/Lazarus中这时可以选择继承来自TComponent,它也实现了IUnknown 但不会释放自身。