您现在的位置是: 软件 > 开发者网络 > 技术跟踪 > 技术理论 > 正文


-明明白白注册表工具
-PHP优化与安全
-Dreamweaver MX 新功能体验
-Visual Basic 图形及图像处理

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  

【责任编辑:方舟】
【发表评论】【关闭窗口】
■ 相关内容
 关于Plug-In实现的描述
 DCOM揭秘之六
 DCOM 揭秘之四 
 DCOM揭秘之三
 DCOM揭秘之二
 分布式组件对象模型DCOM揭秘
感谢 访问天极网,如果您觉得该文章涉及版权问题,请看这里!