`
阿尔萨斯
  • 浏览: 4633339 次
社区版块
存档分类
最新评论

COM聚合

 
阅读更多
聚合源自组件重用。当有两个组件A和B,他们分别实现了自己的接口IA和IB。如果有一个客户程序创建了A对象使得自己可以调用IA的方法,但同时又想获得IB的接口,调用IB的方法。这时候有两种做法:一种是客户程序创建B对象,还有一种方法是A组件内部创建B组件,然后客户通过某种途径调用B的接口方法。
第一种方法,使得客户必须知道有独立的B组件的存在,第二种方法客户可以认为只有一个组件A,组件A实现了两个接口IA和IB。第二种方法可以制造出一种假象,让客户程序编写更加简单。从组件A如何管理组件B的方法上,第二种方法还可以分为两种:包容和聚合。
包容很简单,如果组件IB接口拥有一个方法F(),那么A组件就要实现一个自己的IBEx接口,并实现IBEx::F( )方法,内部调用IB::F()方法。这样,客户也就可以通过调用IBEx::F()来调用IB::F。在这种情况下,客户只知道有IA和IBEx接口,不知道还存在另一个B组件和IB接口。IBEx::F()增加一些代码从而修改IB::F()方法的功能,甚至可以完全丢弃IB::F()方法。
聚合通常用于IB接口的功能完全不需要做任何的修改,就可以直接交给用户使用的情况。这时候,如果IB接口的方法很多,包容就显得很笨拙。因为它不得不对每一个方法作一次包装,尽管什么都不做。COM+对象池就是通过聚合我们的组件,来把我们组件的接口暴露给客户的。聚合方式下,A组件直接将IB接口交给客户,客户就可以调用,但是客户仍然以为是A组件实现了IB接口。如下图:
组件A
IUnknown

组件B
IB

客户直接使用

IA


客户直接使用
客户程序只知道A组件而不知道B组件,并且认为A组件实现了IA和IB接口。因此,当客户创建了A组件(通过CoCreateInstance函数),获取到IUnknown接口时,应该获得的是A组件实现的IUnknown接口。 问题在于B组件有自己的IUnknown的接口实现,如果B组件还是采用一般的方法实现IUnknown接口的话,当客户调用IB::QueryInterface函数,就不会得到IA接口,这当然是不允许的。所以一个支持聚合的组件,它的IUnknown实现必然要有别于普通组件。
B组件应该拥有一个成员变量IUnknown* m_pUnknownOuter;该指针可以指向A组件的IUnknown接口。当客户调用IB::QueryInterface请求时,如果IB接口已经被聚合了,就调用m_pUnknownOuter->QueryInterface方法,这实际上就是调用了A组件的QueryInterface方法。那么,m_pUnknownOuter指针是什么时候被初始化的呢?CoCreateInstance函数和IClassFactory::CreateInstance方法都接受一个IUnknown参数。如果A组件内部想聚合B组件的IB接口,他就会将自己的IUnknown指针传递进去,如果A组件并不想聚合B组件,那么简单的传递一个NULL就行了。
B组件可以根据m_pUnknownOuter是否为NULL,来判断是否被聚合。
B组件实现的具体步骤如下:
1)声明一个INondelegationUnknown接口,该接口拥有IUnknown接口一样的纯虚函数,当然函数名前面均加上Nondelegation前缀。
2)CB类(假设CB类为组件类)继承并实现INondelegationUnknown接口,实现代码和普通组件的IUnknown一样,这用于非聚合的情况下。
3)CB类的构造函数接受IUnknown指针,如果传递进来的是NULL,说明B组件并不被用作聚合。m_pUnknownOuter变量就指向B组件自身的IUnknown接口。如果传递进来的不是NULL,说明被用作聚合。m_pUnknownOuter变量值等于参数值,指向外部组件的IUnknown接口指针。
4)CB类也要继承并实现IUnknown接口。这个接口的QueryInterface函数实现只是调用m_pUnknownOuter->QueryInterfac方法。m_pUnknownOuter究竟代表什么取决于创建组件时是否传递了外部组件的IUnknown指针。
5)B组件的类厂的CreateInstance方法内部创建CB类时,将IUnknown* pUnkownOuter参数传递给构造函数。创建成功后,调用B组件自身的(而不是外部的)NondelegationQueryInterface方法,将自身的INondelegationUnknown传递出去给组件A。
外部组件A要创建组件B的实例,并保存B组件的INondelegationUnknown接口指针。该接口指针是通过上一节5)由B的类厂返回的。m_pUnknownInner变量保存了B接口的INondelegationUnknown接口指针。外部组件调用CoCreateInstance函数创建聚合组件时,iid必须等于IID_IUnknown。
外部组件要修改自己的QueryInterface,当客户程序请求IB接口的时候,将调用m_pUnknownInner->QueryInterface方法。
外部组件可以通过QueryInterface的代码来控制是否聚合B组件所有的接口。如果不想聚合B组件实现的IC接口。可以在输入参数iid等于IID_IC的时候,返回E_NOINTERFACE。
聚合B组件的所有接口的方式称为盲聚合。盲聚合的缺点是:如果B组件实现了IPersist接口,客户调用IPersist::GetClassID方法时,获得的是CLSID_CB,这就暴露了内部的B组件;还有就是B组件实现的接口不了解A组件的状态,如果B组件实现了ISave接口,客户调用了它,却不能正确完成保存组件状态的功能。所以,一般我们要避免使用盲聚合,而要有选择的聚合。
外部组件还有一个重要的任务就是控制内部组件的生命周期。因为内部组件拥有外部组件的IUnknown指针,这时候当调用内部组件的AddRef/Release,改变的是外部组件的引用计数。所以如果需要减少引用计数,应该调用pUnknowOuter(pUnknowOuter其实就是this指针的强制转换)->Release( )。CA类析构时,要采用以下特殊的做法保证不会被过早的析构和多次释放。
m_cRef=1;
IUnknown* pUnknownOuter = this;
pUnknownOuter->AddRef( );
m_pIB->Release( );
需要注意的是CA的析构函数是在A组件自身的引用计数为0时才会被调用,如果没有m_cRef=1这行代码,m_pIB->Release( )会导致析构函数再次被调用。
` 最后,析沟函数将释放组件B。通过调用B的INondelegationUnknown::Release( )方法。
代码如下:
if(m_pUnknownInner!=NULL)
{
m_pUnknownInner->Release ( );
}
以上总结主要来自于<<COM技术内幕>>。但是在ATL中,一切都被隐藏的很多。
ATL7对内部组件的支持
使用ATL7.1创建ATL简单对象(传统COM对象时)向导中可以选择是否支持聚合,默认是支持的。我们现在开始创建内部组件。
按照上图步骤创建组件B,并且实现了IB接口。现在我们为IB增加方法F( )。F()方法很简单,只是弹出一个消息框。
STDMETHODIMP CB::F(void)
{
MessageBox(NULL,"OK","OK",MB_OK);
return S_OK;
}
DECLARE_CLASSFACTORY()-------------
ATL中,类厂是由CComCoClass类的宏DECLARE_CLASSFACTORY()实现的。该宏的定义为:#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(ATL::CComClassFactory)
CComClassFactory实现了IClassFactory接口。最主要的就是实现了创建组件的函数。
// IClassFactory
STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj)
{
ATLASSERT(m_pfnCreateInstance != NULL);
HRESULT hRes = E_POINTER;
if (ppvObj != NULL)
{
*ppvObj = NULL;
// can't ask for anything other than IUnknown when aggregating
if ((pUnkOuter != NULL) && !InlineIsEqualUnknown(riid))
{
ATLTRACE(atlTraceCOM, 0, _T("CComClassFactory: asked for non IUnknown interface while creating an aggregated object"));
hRes = CLASS_E_NOAGGREGATION;
}
else
hRes = m_pfnCreateInstance(pUnkOuter, riid, ppvObj);
}
return hRes;
}
m_pfnCreateInstance是创建COM对象的函数的指针。创建CB类的函数指针是被保存到对象映射表中的用于创建CB类的函数指针。对象映射表是一个_ATL_OBJMAP_ENTRY结构数组,该结构数组还保存了创建类厂的函数指针。该结构数组的初始化是通过B.h中的宏OBJECT_ENTRY_AUTO(__uuidof(B), CB)来完成的。该宏取代了ATL3中的BEGIN_OBJECT_MAP()/OBJECT_ENTRY()/END_OBJECT_MAP()宏。
DECLARE_CLASSFACTORY_EX定义为#define DECLARE_CLASSFACTORY_EX(cf) typedef ATL::CComCreator< ATL::CComObjectCached< cf > > _ClassFactoryCreatorClass;
CComObjectCached类派生自CComClassFactory,该类的作用是使得组件类的生命周期和服务器生命周期相同,所以适用于进程内组件。CComCreator类只提供了一个CreateInstance函数,用来帮助我们创建一个组件类,这里就帮助我们创建类厂。
所以宏DECLARE_CLASSFACTORY()的功能就是:
1)通过从CComClassFactory类派生实现了IClassFactory接口
2)使用CComObjectCached类指定了类厂对象的生命周期和服务器生命周期相同
3)定义了_ClassFactoryCreatorClass类型,该类型的CreateInstance函数可以创建类厂对象,而且是采用多步构造的方式,防止了构造期间的意外析构。
DECLARE_AGGREGATABLE(T)---------------------
该宏的定义如下:
#define DECLARE_AGGREGATABLE(x) public:/
typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >, ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass;
_CreatorClass类型的CreateInstance函数即可以创建独立组件,也可以创建聚合组件,取决于传入的pOuterIUnknown参数是否为NULL。
值得一提的是CComAggObject类通过两次从CComObjectRootEx继承,实现了内部组件的两个IUnknown接口,一个是IUnknown*,一个是INondelegationUnknown*(其实ATL中不使用INondelegationUnknown名字,但是作用一样和INondelegationUnknown一样)。
如果我们在向导中选择不支持聚合,向导将会在我们的CB类内加上宏#define DECLARE_NOT_AGGREGATABLE(x)
#define DECLARE_NOT_AGGREGATABLE(x) public:/
typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >, ATL::CComFailCreator<CLASS_E_NOAGGREGATION> > _CreatorClass;
这样_CreatorClass类型的CreateInstance函数当接收到不为NULL的pOuterIUnknown参数时会失败。
所以,当我们有了ATL的支持后,我们创建内部组件的一切工作只是在向导中选择支持聚合,轻松惬意!
ATL7对外部组件的支持
现在我们另创建一个名为OuterComponent的ATL项目,创建A组件并实现IA接口。创建步骤就不再叙述。A组件是否支持聚合无关紧要,我们这里就默认支持吧。在OuterComponent项目中导入InnerComponent.dll,然后将CComPtr<IUnknown> m_pIB作为CA的成员变量。请参考下面的代码的注释
// A.h : CA 的声明
#pragma once
#include "resource.h" // 主符号
#include "OuterComponent.h"
// CA
class ATL_NO_VTABLE CA :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CA, &CLSID_A>,
public IDispatchImpl<IA, &IID_IA, &LIBID_OuterComponentLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CA()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_A)
BEGIN_COM_MAP(CA)
COM_INTERFACE_ENTRY(IA)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IB,m_spunkIB.p,CLSID_B)//自动聚合IB接口,有了该宏就不需要在FinalConstruct函数中创建B对象了
END_COM_MAP()
DECLARE_GET_CONTROLLING_UNKNOWN()//创建虚函数IUnknown* GetControllingUnknown(),CComContainedObject类会继承并实现该函数,然后将返回最外层组件的IUnknown指针
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
m_spunkIB.Release();//避免两次析构内部组件
}
private:
CComPtr<IUnknown> m_spunkIB;//保存被聚合的内部组件的IUnknown接口
};
OBJECT_ENTRY_AUTO(__uuidof(A), CA)
现在我们来用一下,创建一个客户程序,它导入A和B的组件类型库,然后创建A组件,然后查询IB接口并调用IB::F()方法。
// Client.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#import "../../OuterComponent/OuterComponent/debug/OuterComponent.tlb" no_namespace named_guids raw_interfaces_only
#import "../../InnerComponent/InnerComponent/debug/InnerComponent.tlb" no_namespace named_guids raw_interfaces_only
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
CComPtr<IA> pA;
HRESULT hr=pA.CoCreateInstance(CLSID_A);
CComPtr<IB> pB;
hr=pA->QueryInterface(&pB);
pB->F();
pB.Release();
pA.Release();
::CoUninitialize();
return 0;
}
一切成功。ATL大大简化了我们对于聚合的使用。
陈抒
2005-9-15
分享到:
评论

相关推荐

    测试com聚合,简单的程序

    在"测试com聚合,简单的程序"这个项目中,我们看到主要涉及的是使用ATL(Active Template Library)来实现COM聚合。ATL是微软提供的一个C++库,专门用于简化COM对象的开发。它提供了一系列模板类,可以快速创建轻量...

    Com聚合包容.doc

    com 聚合

    COM 包容与聚合

    **COM聚合**,则是在包容的基础上更进一步,它允许一个COM对象由其他对象的实例组成。这些组成部分被称为聚合部件,它们是独立的COM对象,但对外界而言,它们看起来像是单一对象的一部分。聚合的特点在于,聚合对象...

    最简单的COM实现 - 聚合实现

    在学习和实现COM聚合时,深入理解`IUnknown`接口的作用以及如何正确地管理引用计数至关重要。此外,对COM的其他核心概念如接口、CLSID、 IID以及COM对象的生命周期有清晰的认识,也会对理解聚合有所帮助。

    com聚合代码

    在本压缩包中,"第八章,com的聚合"可能是一个关于COM组件聚合的教程或者案例集,其中包含了多个文件,可能包括源代码、解释文档等,用于帮助学习者理解并掌握COM组件的聚合概念。 COM组件聚合是指一个COM对象可以...

    COM组件聚合例子

    在“COM组件聚合例子”中,我们将探讨如何实现和调用COM组件,这是Windows平台上软件开发的重要组成部分。 COM组件的核心概念是接口,它定义了组件提供的服务和方法。接口是类型安全的,通过指针进行操作,使得不同...

    COM 组件聚合小示例

    COM组件的聚合是指一个COM对象可以包含或“聚合”其他COM对象,形成复合对象。这在不改变外部接口的情况下,能够增强对象的功能。聚合不同于继承,它是一种运行时的关系,而继承是在编译时确定的。在COM中,聚合通常...

    高德地图点聚合效果

    在HTML文件中,通过`&lt;script&gt;`标签引入API的URL,通常为`https://webapi.amap.com/maps?v=1.x&key=YOUR_API_KEY`,其中`YOUR_API_KEY`是你从高德地图开发者平台申请到的API密钥。 接着,创建地图实例并设置初始视图...

    聚合登录平台网站源码 PHP聚合登录源码.zip

    清轩聚合登录平台网站源码,彩虹聚合登录系统二开,后台采用 Layuiadmin 改版,增加了前台界面、开发文档、SDK 文件, 后台增加站点配置功能。 源码主要功能:可以实现中转 QQ、微信、支付宝、微博、百度等平台的...

    百度地图类库 聚合marker

    &lt;script src="http://api.map.baidu.com/api?v=1.2"&gt;&lt;/script&gt; &lt;script src="path/to/markerclusterer.js"&gt;&lt;/script&gt; // 创建地图实例 var map = new BMap.Map("container"); // 初始化地图位置 map.centerAndZoom...

    Android开发,高德地图开启、关闭点聚合功能。

    implementation 'com.amap.api:maps:5.x.x' // 使用最新版本号 } ``` 然后同步项目以下载依赖库。 接下来,我们将讨论如何实现点聚合功能: 1. 创建地图Fragment或MapView:在布局文件中添加MapView,并在代码中...

    ATL 组合与聚合代码

    在COM中,组合和聚合是两种关键的设计模式,它们都是用来实现对象间的关联关系,但方式略有不同。 **组合** 是一种“has-a”关系,类似于面向对象设计中的继承。一个对象(组合对象)包含其他对象(成员对象),并...

    彩虹登录聚合系统网站

    QQ互联申请地址:https://connect.qq.com/, 申请的时候不要用本程序直接去申请,否则不可能通过。需要先搭建一个Discuz论坛, 或者其他论坛程序去申请。申请成功之后再把域名换绑定到本程序。(域名需要已备案)! ...

    聚合关系PPT模板

    ### 聚合关系PPT模板知识点解析 #### 一、理解聚合关系 **聚合关系**(Aggregation)是面向对象设计中的一个重要概念,它描述了一种“has-a”(拥有)的关系,即一个类(整体)包含另一个类(部分)的对象作为其成员...

    Android-实现高德地图的marker聚合功能

    1. **依赖引入**:首先,我们需要在项目的build.gradle文件中引入高德地图SDK以及用于聚合的第三方库,如`com.google.maps.android:android-maps-utils`。 2. **自定义ClusterManager**:高德地图虽然没有内置的...

    【Win32】进程内COM的两种复用方式(包容和聚合)

    本文将深入探讨在进程内COM组件的两种复用方式:包容(Aggregation)和聚合(Containment)。 1. 包容(Aggregation) 包容是一种特殊形式的COM组件关系,其中一个COM对象包含另一个COM对象,作为其内部成员。在...

    22.(leaflet篇)leaflet区域聚合点(点击后散开并进行合理定位).zip

    本教程将专注于Leaflet中的一个特定特性——区域聚合点(Marker Clustering)。通过这个功能,我们可以有效地管理大量地图标记,避免在屏幕中因标记过多而导致的混乱。 首先,我们要理解什么是区域聚合点。在地图上...

    leaflet点聚合 案例,大数据,34万个聚合点都没有问题

    &lt;script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"&gt;&lt;/script&gt; &lt;script src="Leaflet.markercluster-1.3.0/dist/leaflet.markercluster.js"&gt;&lt;/script&gt; ``` 接下来,我们需要创建一个Leaflet地图实例,...

    亲测彩虹聚合登录系统源码开心版 一站式社会化账号登录系统

    QQ互联申请地址:[url]https://connect.qq.com/ 申请的时候不要用本程序直接去申请,否则不可能通过。 申请的时候建议搭建一个博客去申请,实测博客比较容易申请通过。 申请的时候回调地址填写:http://你的域名/...

    arcgis for js 点聚合

    &lt;script src="https://js.arcgis.com/4.x/"&gt;&lt;/script&gt; require(["esri/Map", "esri/views/MapView", "esri/layers/FeatureLayer", "esri/layers/ClusterLayer"], function(Map, MapView, FeatureLayer, ...

Global site tag (gtag.js) - Google Analytics