65.9K
CodeProject 正在变化。 阅读更多。
Home

COM 概念:揭示聚合

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (29投票s)

2003 年 10 月 31 日

6分钟阅读

viewsIcon

89149

downloadIcon

1323

揭示聚合

引言

聚合是 COM 中实现重用性的一种技术。聚合是包含(Containment)的一种特殊形式,在这种形式中,程序员不必在外部组件中编写代码来转发/委托调用给内部组件,从而使程序员的生活更轻松。在聚合中,外部组件直接将内部组件实现的接口的控制权交给客户端,外部组件则退出该过程。在聚合中,内部组件的接口直接暴露给客户端,这与包含不同。

这可能导致一个问题,即可能违反下面陈述的基本 QueryInterface 规则。

将内部组件的接口指针交给客户端,可能会导致客户端看到组件的不同视图,因为客户端可以调用该接口指针上的 QueryInterface 来获取内部组件上的 IUnknown 接口指针。外部 IUnknown 和内部 IUnknown 将具有不同的 QueryInterface 实现,因此客户端将看到不同的视图。聚合的基本概念是客户端应该独立于聚合技术的实现,并且客户端不应该知道外部组件正在聚合内部组件。外部 IUnknown 和内部 IUnknown 实现了一组不同的接口,因此为客户端提供了组件的双重视图。

详细说明

考虑以下情况:有两个组件,分别称为 CScientificCBasic,它们分别提供科学计算和基本数学运算的功能。CBasic 组件公开了两个接口,称为 IAddSubIMultiDivCScientific 类实现了一个 ITrignometry 接口来提供三角函数运算。

现在,假设有一些客户端需要科学功能以及基本功能的加减运算。可以通过实现 ITrignometry 接口并聚合 IAddSub 接口来满足这些客户端的需求。IAddSub 的实现由内部组件提供,外部组件将直接将 IAddSub 接口指针交给客户端,以提供内部组件的服务。外部组件将不会委托 IMultiDiv 接口,因此客户端将看不到该接口。

聚合的妙处在于内部组件上 IUnknown 接口的实现。内部 IUnknown 不应暴露给客户端,因此客户端应该只看到一个 IUnknown 接口,即外部 IUnknown(控制 IUnknown)。

通过在 IAddSub 接口指针上调用 QueryInterface 来请求 IUnknown,应该返回外部组件的 IUnknown,因此内部组件应该使用外部组件为 IUnknown 提供的实现。

外部 IUnknown 到内部组件

为了转发/委托调用到外部 IUnknown,内部组件需要外部组件的 IUnknown 接口指针。外部组件在创建内部组件时传递其 IUnknown 接口指针。外部组件调用 CoCreateInstance,并在 CoCreateInstance 的第二个参数中传递其 IUnknown 指针。如果此参数非 NULL,则表示该组件正在被聚合;否则,表示该组件未被聚合。

内部组件实现

内部组件需要实现两个 IUnknown 接口来支持聚合。控制内部组件生命周期的接口称为非委托 IUnknown,而转发对 IUnknown 成员函数的调用到外部组件的接口称为委托 IUnknown

非委托 IUnknown 绝不会暴露给客户端,只有外部组件才能获取非委托 IUnknown 接口指针的指针。外部组件通过非委托接口指针控制内部组件的生命周期。每当客户端通过在 IAddSub 接口指针上调用 QueryInterface 来请求 IUnknown 指针时,客户端应该获得外部组件的 IUnknown 指针。非委托 IUnknown 的实现将需要内部组件中的两个 IUnknown,因此我们将定义一个名为 INonDelegateUnknown 的新接口,它将具有与 IUnknown 相同的虚表布局。COM 的本质在于 vtable 布局,因此 INonDelegateUnknown 的成员函数名称将以“NonDelegate”作为前缀。

 struct INonDelegateUnknown {
      virtual HRESULT __stdcall NonDelegateQueryInterface ( const IID&, 
           void**)=0;
      virtual ULONG __stdcall NonDelegateAddRef()=0;
      virtual ULONG __stdcall NonDelegateRelease()=0;
  }; 

NonDelegateAddRefNonDelegateRelease 的实现将分别增加和减少内部组件的引用计数。

需要修改 NonDelegateQueryInterface 的实现,以便每当外部组件请求内部组件上的 IUnknown 接口指针时,内部组件都应将非委托 IUnknown 指针交给外部组件,而不是 IUnknown 指针。外部组件只能在创建内部组件时获取内部组件上的非委托 IUnknown 指针,因此 NonDelegateQueryInterface 将在对应于内部组件的类对象的 CreateInstance 中被调用。NonDelegateQueryInterface 应该对 IUnknown 接口和 IAddSub 接口进行检查,因为外部组件只能从内部组件获取这两个接口,而对于其他接口指针(例如 IMultiDiv),QueryInterface 应返回 E_NOINTERFACE

HRESULT __stdcall CBasic::NonDelegateQueryInterface (const IID& iid,
    void **ppv)
  {
      if (iid == IID_IUnknown) {

  // When the AddRef will be called on the ppv before returning
  // from this function,the reference count of the inner component
  // is incremented by 1.

             *ppv = static_cast < INonDelegateUnknown*>(this);
      }
      else if (iid = IID_IAddSub) {

  // When the AddRef will be called on the ppv before 
  // returning from this function,the reference count of the outer component
  // is incremented by 1. This is because that the IUnknown
  // implementation of IAddSub interface on inner component should 
  // delegate to the outer component’s controlling IUnknown implementation. 
             *ppv = static_cast < IAddSub*<(this);
      }
      else {
             *ppv = NULL;
             return E_NOINTERFACE;
      } 
    reinterpret_cast < IUnknown*<(*ppv)->AddRef();
    return S_OK;
  }

为什么我们需要将 this 指针类型转换为 INonDelegateUnknown

类型转换 this 指针可确保返回 INonDelegateUnknown 接口指针。

当外部组件查询属于内部组件的 INonDelegateUnknown 指针以外的接口时,外部组件的引用计数会增加。外部组件的引用计数永远不会达到 0,因此它永远不会从内存中释放。因此,每当外部组件查询内部组件实现的任何接口时,它都应该在其控制 IUnknown 指针上调用 Release。每当外部组件查询内部组件上的 INonDelegateUnknown 指针时,内部组件的引用计数会增加。

内部组件类对象的 IClassFactory 实现已修改,以将 INonDelegateUnknown 指针传递给外部组件。外部组件在创建内部组件时只能请求 IUnknown 接口指针,因为在创建之后,所有 QueryInterface 调用都将委托给外部 Unknown。类工厂需要返回一个指向非委托 unknown 的指针,因此它将在 CreateInstance 的实现中调用 NonDelegateQueryInterface

在执行客户端应用程序(AggregationClient.exe)之前,需要使用 regsvr32 工具注册 COM 服务器(AggregationSample.dll & AggregableObject.dll)。代码已正确注释,以解释聚合技术中涉及的关键步骤。

© . All rights reserved.