| | | DCOM揭秘之五 | | 2001-08-22·
·QQ新人类··yesky
| 上一页 1 2 COM对象--“CBeepObj”
COM服务器必须实现至少一个COM对象。我们使用一个单一的称为“CBeepObj”的对象。该对象一个最有趣的地方是它的代码完全是由ATL向导产生的。它的对象定义也非常地紧凑,该类的定义放在BeepObj.h中:
// BeepObj.h : Declaration of the CBeepObj #include "resource.h" // main symbols ////////////////////////////////////////////////////// // CBeepObj class ATL_NO_VTABLE CBeepObj : public CComObjectRootEx, public CComCoClass, public IBeepObj { public: CBeepObj() { }
DECLARE_REGISTRY_RESOURCEID(IDR_BEEPOBJ)
BEGIN_COM_MAP(CBeepObj) COM_INTERFACE_ENTRY(IBeepObj) END_COM_MAP()
// IBeepObj public: STDMETHOD(Beep)(/*[in]*/ long lDuration); };
|
这个简单的头文件定义了大量的功能,以下部分将予以讲解。
对象继承
在这些代码中,你首先留意到的可能就是多重继承了。我们的COM对象拥有三个基类。这些基类是模板类,用来实现我们对象的基本COM功能。每一个的类都定义了一个特定的COM动作。
CComObjectRootEx< > 和 CComObjectRoot< > 是ATL对象类的根。这些类处理全部的引用计数和COM类管理,包括有三个需要的IUnknown接口的实现,即QueryInterface(), AddRef()和Release()。当我们的CBeepObj对象被服务器创建时,基类将会在整个对象的生存期间跟踪它。
CComObjectRootEx模板指定了CComSingleThreadModel参数。单线程意味着COM对象不必处理多线程的访问。在该对象的设置中,我们指定为“Apartment threading(独立线程)”。独立线程使用一个windows信息循环来同步COM对象的访问。这个方式是最简单的,因为它消除了多线程的问题。
CComCoClass< >定义了创建ATL COM对象的类制造器(Class factories)。类制造器是特别的COM类,它是用来创建COM对象的。CComCoClass使用一个默认类型的类制造器,并且允许aggregation(集合)。
IBeepObj是该服务器实现的接口。一个接口被定义一个C++结构体(你可能会记起C++结构体的使用和类相似,但是只能使用公共的成员)。如果你查看自动产生的BeepServer.h文件,你将会发现MIDL已经创建了一个我们接口的定义。
interface DECLSPEC_UUID( "36ECA947-5DC5-11D1-BD6F-204C4F4F5020") IBeepObj : public IUnknown { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Beep( /* [in] */ long lDuration) = 0; };
|
DECLSPEC_UUID宏让编译器将一个GUID和接口名字关联起来。要注意到我们的单一方法“Beep”被定义为一个纯的抽象函数。当CBeepObj被定义时,将必须要提供一个该函数的实现。
这个类定义的一个特别地方是它拥有ATL_NO_VTABLE属性。该宏是一个优化,它可让对象的初始化更快。
类定义
我们的对象使用一个默认的构造器。如果需要,你可以加入特别的初始化动作,不过有一些限制。使用ATL_NO_VTABLE的一个限制是不允许在构造器中调用任何抽象方法。要进行复杂的初始化,可使用FinalConstruct方法(由CComObjectRootEx继承而来)。如果你想使用FinalConstruct,可在类定义中声明它,覆盖ATL的默认。它将会自动地被ATL构架调用。(FinalConstruct通常被用来创建aggregated对象)
DECLARE_REGISTRY_RESOURCEID()宏用来在系统的注册表中注册COM对象。该宏的参数IDR_BEEPOBJ指向工程的一个资源。这是一个特别类型的资源,它下载MIDL产生的“.rgs”文件。
BEGIN_COM_MAP是一个宏,它定义了CComObjectRoot< >类将会管理的COM接口的一个数组。这个类有一个接口--IBeepObj。IBeepObj是我们的自定义接口。COM对象实现超过一个接口是很常见的。所有支持的接口都会在这里展示,还包括有顶部类定义中的类继承。
方法
最后,我们将谈到方法。作为一个应用的编程者,我们的兴趣主要在这部分的代码。我们的单一Beep()方法由以下行定义:
STDMETHOD(Beep)(/*[in]*/ LONG duration);
STDMETHOD是一个OLE宏,它被转换为:
typedef LONG HRESULT; #define STDMETHODCALLTYPE __stdcall #define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method
|
我们可以一个更熟悉的C++样式编写该定义,如下:
virtual long _stdcall Beep(long lDuration);
我们将可在BeepObj.cpp模块中找到这个方法的代码。由于该COM对象仅有一个方法,因此COM对象的源代码比较少。所有对象的COM逻辑都被定义在ATL的模板类中。我们剩下的工作就是编写真正的应用代码。在你编写真正的应用时,你的所有注意力将应该集中在这个模块上。
STDMETHODIMP Beep(long lDuration) { ::Beep( 660, lDuration ); return S_OK; }
|
再次,该函数定义转换为一个标准的函数调用
long _stdcall CBeepObj::Beep( long lDuration )
API beep程序接受两个参数:发声的频率和以毫秒算的持续时间。如果你使用的是Windows 95。这两个参数都可以忽略,从而得到默认的beep声。范围操作符“::”是重要的,不过很容易被忘掉。如果你漏了,该方法将会调用自己。
_stdcall标签告诉编译器该对象使用标准的windows调用协定。默认的情况下,C和C++使用__cdecl调用协定。这些可指示编译器参数的放置顺序和堆栈的移除。Win32 COM使用_stdcall协定,其它的操作系统可能使用不同的调用协定。
注意到我们的Beep()方法返回一个状态S_OK。这并不意味着调用者一直会得到一个成功的返回状态。要记得调用COM方法和标准的C++函数是不一样的。在调用程序(客户)和COM服务器间存在在一个完整的COM层。
CBeepObj::Beep()方法返回S_OK是完全可能的,不过连接可在一个COM调用的中间失去。虽然该函数将返回S_OK,不过调用的客户将得到某些RPC错误指示出失败。函数的结果必须由COM返回到客户端。
在这个例子中,COM服务器作为一个进程内的服务器运行。通过DLL的连接是非常紧密的,因此发生传送错误的机会很少。在以后的例子中,当我们的COM服务器运行在一个远程的计算机时,将有明显的不同。网络错误将会是很常见的,因此你需要在设计应用时处理它们。
服务器注册
COM子系统使用Windows的注册表来查找和启动所有的COM对象。每个COM服务器负责自己注册,即在注册表中写入相关的项目。便利的是,该任务大部分可由ATL、MIDL和ATL向导自动完成。
MIDL产生的其中一个文件是一个注册脚本。该脚本包含了成功操作我们服务器的全部定义。以下就是产生的脚本:
HKCR { BeepObj.BeepObj.1 = s 'BeepObj Class' { CLSID = s '{861BFE30-56B9-11D1-BD65-204C4F4F5020}' } BeepObj.BeepObj = s 'BeepObj Class' { CurVer = s 'BeepObj.BeepObj.1' } NoRemove CLSID { ForceRemove {861BFE30-56B9-11D1-BD65-204C4F4F5020} = s 'BeepObj Class' { ProgID = s 'BeepObj.BeepObj.1' VersionIndependentProgID = s 'BeepObj.BeepObj' ForceRemove 'Programmable' InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } } } }
|
注册表脚本
你可能对注册表的.REG脚本非常熟悉。.RGS脚本也是类似的,不过它使用的是一个完全不同的句法,而且只能被ATL作对象注册使用。这些句法允许作简单的变量取代,如在%MODULE%中的变量。这些脚本被ATL的注册组件调用(Registrar)。它由对象头文件的一个宏定义:
DECLARE_REGISTRY_RESOURCEID(IDR_BEEPOBJ)
基本上,当服务器调用CComModule::RegisterServer()时,便会使用这些脚本来载入注册表的设置,并在调用CComModule::UnregisterServer()时移除它们。所有COM的注册键都放在HKEY_CLASSES_ROOT。以下就是被设置的注册键:
BeepObj.BeepObj.1 - 该类的目前版本
BeepObj.BeepObj - 通过名字标识COM对象
CLSID - 该对象的唯一类标识
在CLISD下还有一些子键:
ProgID -programmatic标识
VersionIndependentProgID -将一个ProgID和一个CLSID关联
InprocServer32 - 定义一个服务器的类型(作为一个DLL)。根据服务器的类型而有不同(进程内,本地,远程服务器)
ThreadingModel -对象的COM线程模式
TypeLib - 服务器类库的GUID
上一页 1 2 | | | 感谢
访问天极网,如果您觉得该文章涉及版权问题,请看这里!
|
|