| 持IClassFactory接口的对象;例程DllCanUnloadNow判断DLL是否可从内存卸载;DllRegisterServer 和DllUnregisterServer负责DLL的注册和解除注册,其具体的功能由ComServer实现。 接口类的具体实现 好了,现在自动生成代码的来龙去脉已经解释清楚了,下一步就是由我们来添加接口方法的实现代码。在function TSample.GetCOMInfo的部分 添加如下代码。我写的例子很简单,仅仅是根据传递的参数组织一条字符串并返回。以此证明接口正确调用并执行了该代码: function TSample.GetCOMInfo(ValInt: SYSINT;const ValStr: WideString): WideString; const Server1 = 1; Server2 = 2; Server3 = 3; var s : string; begin s := ''''This is COM server : ''''; case ValInt of Server1: s := s + ''''Server1''''; Server2: s := s + ''''Server2''''; Server3: s := s + ''''Server3''''; end; s := s + #13 + #10 + ''''Execute client is '''' + ValStr; Result := s; end; 注册、创建COM对象及调用接口 随便建立一个Application用于测试上面的COM。必要的代码很少,创建一个接口的实例然后执行它的方法。当然我们得先行注册COM,否则调用 根据CLSID找不接口的话,将报告“无法向注册表写入项”。如果接口定义不一致,则会报告“Interface not supported”。 编译上面的这个COM工程,然后选择菜单“Run – Register ActiveX Server”,或者通过Windows下system/system32目录中的regsvr32.exe程 序注册编译好的DLL文件。regsvr32的具体参数可以通过regsvr32/?来获得。对于进程外(EXE型)的COM对象,执行一次应用程序就注册了。 提示DLL注册成功后,就应该可以正确执行下列客户端程序了: uses ComObj, pCOM_TLB; procedure Ttest.Button1Click(Sender: TObject); var COMSvr : ISample; retStr : string; begin COMSvr := CreateComObject(CLASS_Sample) as ISample; if COMSvr <> nil then begin retStr := COMSvr.GetCOMInfo(2,''''client 2''''); showmessage(retStr); COMSvr := nil; end else showmessage(''''接口创建不成功''''); end; 最终值是从当前程序外的一个“接口”返回的,我们甚至可以不知道这个接口的实现!第一次接触COM的人,成功执行此程序并弹出对话框后, 也许会体会到一种技术如斯奇妙的感觉,因为你仅仅调用了“接口”,就可以完成你猜测中的东西。 创建一个分布式DCOM(自动化接口) IDispatch 在delphi6之前的版本中,所有接口的祖先都是IUnknown,后来为了避免跨平台操作中接口概念的模糊,又引入了IInterface接口。 使用向导生成DCOM的步骤和COM几乎一致。而生成的代码仅将接口类的父类换为TAutoObject,类工厂类换为TAutoObjectFactory。这其实没有 太大的不同,因为TAutoObject等于是一个标准COM外加IDispatch接口,而TAutoObjectFactory是从TTypedComObjectFactory直接继承的: TAutoObject = class(TTypedComObject, IDispatch) TAutoObjectFactory = class(TTypedComObjectFactory) 自动化服务器支持双重接口,而且必须实现IDispatch。因讨论范畴限制,本文只能简单提出,IDispatch是DCOM和COM技术实现上的一个重要区 别。打开_TLB.pas单元,可以找到Ixxx = interface(IDispatch)和Ixxx = dispinterface的定义,这在前面COM的例子里面是没有的。 创建过程中的差异 使用类型库编辑器的时候,有两处和COM不同的地方。首先Return Type必须选择HRESULT,否则会提示错误,这是为了满足双重接口的需要。当 Return Type选择HRESULT后,你会发现方法定义将变成procedure(过程)而不是预想中的function(函数)。 怎么才能让方法有返回值呢?还需要在Parameters最后多添加一个参数,然后将该参数改名与方法名一致,设置参数类型为指针(如果找不到 某种类型的指针类型,可以直接在类型后面加*,如图,BSTR*是BSTR的指针类型)。最后在Modifier列设置Parameter Flags为RetVal,同时 Out将被自动选中,而In将被取消。 刷新后,得到下列代码。添加方法的具体实现,大功告成: TSampleAuto = class(TAutoObject, ISampleAuto) protected function GetAutoSerInfo(ValInt: SYSINT;const ValStr: WideString): WideString; safecall; end; 远程接口调用 远程接口的调用需要使用CreateRemoteComObject函数,其它如接口的声明等等与COM接口调用相同。CreateRemoteComObject函数比 CreateComObject 多了一个参数,即服务器的计算机名称,这样就比COM多出了远程调用的查询能力。前面“接口定义说明”一节的代码可以对 照CreateComObject、CreateRemoteComObject的区别。 自定义COM的对象 接口一个重要的好处是:发布一个接口,可以不断更新其功能而不用升级客户端。因为不论应用升级还是业务改变,客户端的调用方式都是一 致的。 既然我们已经弄清楚Delphi是怎样实现一个接口的,那能否不使用向导,自己定义接口呢?这样做可以用一个接口继承出不同的接口实现类, 来完成不同的功能。同时也方便了小组开发、客户端开发、进程内/外同步编译以及调试。 接口单元:xxx_TLB.pas 前面略讲了接口的定义需要注意的方面。接口除了没有实例化外,它与普通类还有以下区别:接口中不能定义字段,所有属性的读写必须由方 法实现;接口没有构造和析构函数,所有成员都是public;接口内的方法不能定义为virtual,dynamic,abstract,override。 首先我们要建立一个接口。前面讲过接口的定义只存在于一个地方,即xxx_TLB.pas单元里面。使用类型库编辑器可以产生这样一个单元。还是 在新建项目的ActiveX页,选择最后一个图标(Type Library)打开类型库编辑器,按F12键就可以看到TLB文件(保存为.tlb)了。没有定义任 何接口的时候,TLB文件里除了一大段注释外只定义了LIBID(类型库的GUID)。假如关闭了类型库编辑器也没有关系,可以随时通过菜单View – Type Library打开它。 先建立一个新接口(使用向导的话这步已经自动完成了),然后如前面操作一样建立方法、属性…生成的TLB文件内容与向导生成_TLB单元大致 相同,但仅有定义,缺乏“co+类名”之类的接口创建代码。 再观察代码,将发现接口是从IDispatch继承的,必须将这里的IDispatch改为IUnknown。保存将会得到.tlb文件,而我们想要的是一个单元 (.pas)文件,仅仅为了声明接口,所以把代码拷贝复制并保存到一个新的Unit。 自定义CLSID 从注册和调用部分可以看出CLSID的重要作用。CLSID是一个GUID(全局唯一接口表示符),用来标识对象。GUID是一个16个字节长的128位二进 制数据。Delphi声明一个GUID常量的语法是: Class_XXXXX : TGUID = ''''{xxxxxxxx-xxxxx-xxxxx-xxxxx-xxxxxxxx}''''; 在Delphi的编辑界面按Ctrl+Shift+G键可以自动生成等号后的数据串。GUID的声明并不一定在_TLB单元里面,任何地方都可以声明并引用它。 接口类声明与实现 新建一个ActiveX Library工程,加入刚才定义的TLB单元,再新建一个Unit。我的TLB单元取名为MyDef_TLB.pas,定义了一个接口 IMyInterface = interface(IUnknown),以及一个方法function SampleMethod(val: Smallint): SYSINT; safecall;现在让我们看看全部接口 类声明及实现的代码: unit uMyDefCOM; interface uses ComObj, Comserv, ActiveX, MyDef_TLB; const Class_MySvr : TGUID = ''''{1C0E5D5A-B824-44A4-AF6C-478363581D43}''''; type TMyIClass = class(TComObject, IMyInterface) procedure Initialize; override; destructor Destroy; override; private FInitVal : word; public function SampleMethod(val: Smallint): SYSINT; safecall; end; TMySvrFactory = class(TComObjectFactory) procedure UpdateRegistry(Register:Boolean);override; end; implementation { TMyIClass } procedure TMyIClass.Initialize; begin inherited; FInitVal := 100; end; destructor TMyIClass.Destroy; begin inherited; end; function TMyIClass.SampleMethod(val: Smallint): SYSINT; begin Result := val + FInitVal; end; { TMySvrFactory } procedure TMySvrFactory.UpdateRegistry(Register: Boolean); begin inherited; if Register then begin CreateRegKey(''''MyApp\''''+ClassName, ''''GUID'''', GUIDToString(Class_MySvr)); end else begin DeleteRegKey(''''MyApp\''''+ClassName); end; end; initialization TMySvrFactory.Create(ComServer, TMyIClass, Class_MySvr, ''''MySvr'''', '''''''', ciMultiInstance, tmApartment); end. Class_MySvr是自定义的CLSID,TMyIClass是接口实现类,TMySvrFactory是类工厂类。 COM对象的初始化 procedure Initialize是接口的初始化过程,而不是常见的Create方法。当客户端创建接口后,将首先执行里面的代码,与Create的作用一样 。一个COM对象的生存周期内,难免需要初始化类成员或者设置变量的初值,所以经常需要重载这个过程。 相对应的,destructor Destroy则和类的标准析构过程一样,作用也相同。 类工厂注册 在代码的最后部分,假如使用TComObjectFactory来注册,就和前面所讲的完全一样了。我在这里刻意用类TMySvrFactory继承了一次,并且重 载了UpdateRegistry 方法,以便向注册表中写入额外的内容。这是种小技巧,希望大家根据本文的思路,摸清COM/DCOM对象的Delphi实现结构 后,可以举一反三。毕竟随心所欲的控制COM对象,能提供的功能远不如此。 (本文所有代码在Delphi6、Delphi7下编译执行通过) 全文完。 发表于 2005年05月27日 4:26 PM
上一页 [1] [2] |