COM 概念:揭示聚合






4.84/5 (29投票s)
2003 年 10 月 31 日
6分钟阅读

89149

1323
揭示聚合
引言
聚合是 COM 中实现重用性的一种技术。聚合是包含(Containment)的一种特殊形式,在这种形式中,程序员不必在外部组件中编写代码来转发/委托调用给内部组件,从而使程序员的生活更轻松。在聚合中,外部组件直接将内部组件实现的接口的控制权交给客户端,外部组件则退出该过程。在聚合中,内部组件的接口直接暴露给客户端,这与包含不同。
这可能导致一个问题,即可能违反下面陈述的基本 QueryInterface
规则。
将内部组件的接口指针交给客户端,可能会导致客户端看到组件的不同视图,因为客户端可以调用该接口指针上的 QueryInterface
来获取内部组件上的 IUnknown
接口指针。外部 IUnknown
和内部 IUnknown
将具有不同的 QueryInterface
实现,因此客户端将看到不同的视图。聚合的基本概念是客户端应该独立于聚合技术的实现,并且客户端不应该知道外部组件正在聚合内部组件。外部 IUnknown
和内部 IUnknown
实现了一组不同的接口,因此为客户端提供了组件的双重视图。
详细说明
考虑以下情况:有两个组件,分别称为 CScientific
和 CBasic
,它们分别提供科学计算和基本数学运算的功能。CBasic
组件公开了两个接口,称为 IAddSub
和 IMultiDiv
。CScientific
类实现了一个 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; };
NonDelegateAddRef
和 NonDelegateRelease
的实现将分别增加和减少内部组件的引用计数。
需要修改 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)。代码已正确注释,以解释聚合技术中涉及的关键步骤。