用 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 但不会释放自身。