您的位置:首页 > 其它

组件对象模型与ATL实现

2011-08-09 23:17 316 查看
[align=center]组件对象模型与ATL实现[/align]
[align=center]Component Object Model and Implementation with ATL[/align]
应朋友之托,写这篇文章。
cheungmine
2007-10-10

本文所讲解的构建COM对象模型的技术主要面向对COM开发有一定经验却又难领悟其精髓的人,也许在高手看来,本文不过是东搬西凑出来的杂合体。然而,这是在COM领域实现大规模软件的基础。全部内容来自于我6年左右的COM学习和开发大型软件的经验。阅读并理解这篇文章,首先要求你是一名Windows平台的C++程序员,而且相当熟悉COM,并且熟练使用ATL开发。

一 概述

微软组件对象模型(COM)的出现是软件工业发展的一个重要进步。尽管到目前为止,它还主要运行于微软(MS)的操作系统平台。无论对COM喜欢或厌恶,它都充斥着整个互联网和Windows的计算环境。COM以难学易用而著称,与它一起恶名昭彰的还有微软的另外一个名词——ActiveX,我们称为控件。等到你真正按照示例代码实现了一个PolygonCtl或BullEye控件的时候,你才真正理解了一点COM的思想和方法。COM是理论,ActiveX是技术。深谙COM的精髓,熟练掌握ActiveX编程技术,是高级Windows程序员必须做到的。(有人提到MFC,遗憾的是,这是一个和VB一样应该抛弃的技术)。

本文不是讲解COM是什么的文章,也不是讲解如何进行ActiveX编程。我在这里要描述的是如何应用COM技术,建立类似于DOM(Document Object Model)的内容。也就是使用COM实现设计模式方面的问题。何谓设计模式,说白了,就是一种模型——在COM领域称为对象模型,它由一组互相关联的COM对象组合而成,存在于同一个类型库(type library)中,基本以DLL的面目出现。比如,搞地理信息系统的人耳熟能详:MapObjects(MO)、ArcObjects(AO)、SuperMap等等。这些东西是什么?它们就是使用COM技术实现的模式——对象模型。微软使用COM实现了W3C的XML解析器标准。一个HTML文件在浏览器(Internet
Expolorer,IE)内部被实例化成DOM模型——一种典型的树状结构。使用JavaScript的人比较熟悉的window、document这些对象都是DOM中的组件。我要讲的就是,如何在架构的层次上实现上面这些东西。

作为一名COM程序员(当然不仅仅是开发COM,我就要同时写Web Services、网页、JavaScript AJAX、OpenGL、ACE、图形算法、Oracle OCI、C/C++等各种程序),必须精读过下面3本书,这也是我这篇文章的主要参考书:
1)COM技术内幕(Inside COM——by Dale Rogerson);
2)ATL技术内幕(ATL Internals——by Christopher Tavares, Kirk Fertitta, Brent Rector, Chris Sells);
3)COM本质论(Essential COM——by Don Box)。

其中,第2本书《ATL Internals》的——第8章 集合和枚举器——尤其是你必须弄清楚的,即:
Chapter 8. Collections and Enumerators。

原理在那里讲的很清楚了,我只是依样画葫芦告诉读者该如何应用。因为有必要指出的是:按照《ATL Internals: Working with ATL 8, Second Edition》一书的讲解例子,会出现编译不通过的情形。所以,我的实现或许对你有帮助。而且,系统提供的atlcom.h头文件实现的集合索引是从1开始的,这对我们习惯了从0开始的家伙,就如同让惯用右手的人使用左手吃饭一样不方便。我把它也一道改为0-based。

二 设计和初步建立对象模型

为便于说明问题,我以地图控件开发为例,目标是建立类似下面的对象模型,这是一种典型的树状结构:

<Canvas>
|— <Layers>
|— <Layer>
|— <Layer>
......
|— <Layer>
|— <Shapes>
|— <Shape>
|— <Shape>
... ...
|— <Shape>

具体步骤如下:

第一步 创建类型库

打开VS2005创建ATL项目,可以取名为:MapLib。应用程序设置为:动态连接库。(绝对不要属性化和支持MFC)。按[完成]。然后设置项目属性[字符集]为未设置(我个人尤其讨厌使用Unicode字符集)。
查看IDL文件,如下:
[align=left]// MapLib.idl : MapLib的IDL 源[/align]
[align=left]//[/align]
[align=left] [/align]
[align=left]//此文件将由MIDL 工具处理以[/align]
[align=left]//产生类型库(MapLib.tlb)和封送处理代码。[/align]
[align=left] [/align]
[align=left]import"oaidl.idl";[/align]
[align=left]import"ocidl.idl";[/align]
[align=left] [/align]
[align=left][[/align]
[align=left] uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),[/align]
[align=left] version(1.0),[/align]
[align=left] helpstring("MapLib 1.0类型库")[/align]
[align=left]][/align]
[align=left]library MapLibLib[/align]
[align=left]{[/align]
[align=left] importlib("stdole2.tlb");[/align]
};

第二步 添加Canvas控件

首先,我们这里要添加的Canvas组件是一个含有窗口控制的ActiveX控件,同时它也是MapLib对象模型的根组件。所以,选择向MapLib添加新的类,它属于[ATL]类别的[ATL控件]。选中后按[添加]按钮,出现[ATL控件向导 - MapLib]对话框,按下面的要求填写:
[名称]
C++/简称:Canvas。其他默认。
[选项]
控件类型/标准控件。线程模型/单元。支持/连接点/已授权。其他默认。
[接口]
可以全部支持。
[外观]
视图状态/不透明/单色背景。其他/全选中。杂项状态/全不选。其他默认。
[常用属性]
你可以选中几个简单的,如:Appearance和Background Color。

按[完成]按钮。其实,上面的很多选项都可以以后添加,要求你熟悉ATL向导生成的代码。本文为简单起见,忽略了许多实际需要的因素。记住一点:在任何时候绝对不要使用属性化。这个在VS2003里作为默认选中的选项,在VC2005里被去掉了。首先,我不喜欢属性化。其次,属性化看似简单,但引入了另一个复杂性。微软在新版本里去掉默认属性化说明属性化的确有相当的负作用。
好拉,让我们再看看类型库(MapLib.idl):
[align=left]// MapLib.idl : MapLib的IDL 源[/align]
[align=left]//[/align]
[align=left] [/align]
[align=left]//此文件将由MIDL 工具处理以[/align]
[align=left]//产生类型库(MapLib.tlb)和封送处理代码。[/align]
[align=left] [/align]
[align=left]#include"olectl.h"[/align]
[align=left]import"oaidl.idl";[/align]
[align=left]import"ocidl.idl";[/align]
[align=left] [/align]
[align=left][[/align]
[align=left] object,[/align]
[align=left] uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),[/align]
[align=left] dual,[/align]
[align=left] nonextensible,[/align]
[align=left] helpstring("ICanvas接口"),[/align]
[align=left] pointer_default(unique)[/align]
[align=left]][/align]
[align=left]interface ICanvas : IDispatch{[/align]
[align=left] [propput,bindable,requestedit,id(DISPID_BACKCOLOR)][/align]
[align=left] HRESULT BackColor([in]OLE_COLOR clr);[/align]
[align=left] [propget,bindable,requestedit,id(DISPID_BACKCOLOR)][/align]
[align=left] HRESULT BackColor([out,retval]OLE_COLOR* pclr);[/align]
[align=left] [propput,bindable,requestedit,id(DISPID_APPEARANCE)][/align]
[align=left] HRESULT Appearance([in]short nAppearance);[/align]
[align=left] [propget,bindable,requestedit,id(DISPID_APPEARANCE)][/align]
[align=left] HRESULT Appearance([out,retval]short* pnAppearance);[/align]
[align=left]};[/align]
[align=left] [/align]
[align=left][[/align]
[align=left] uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),[/align]
[align=left] version(1.0),[/align]
[align=left] helpstring("MapLib 1.0类型库")[/align]
[align=left]][/align]
[align=left]library MapLibLib[/align]
[align=left]{[/align]
[align=left] importlib("stdole2.tlb");[/align]
[align=left] [[/align]
[align=left] uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),[/align]
[align=left] control,[/align]
[align=left] helpstring("Canvas Class")[/align]
[align=left] ][/align]
[align=left] coclass Canvas[/align]
[align=left] {[/align]
[align=left] [default]interface ICanvas;[/align]
[align=left] };[/align]
};

遗憾的是,这不是我需要的结果,我们需要修改类型库文件,修改后的结果如下:
[align=left]// MapLib.idl : MapLib的IDL 源[/align]
[align=left]//[/align]
[align=left] [/align]
[align=left]//此文件将由MIDL 工具处理以[/align]
[align=left]//产生类型库(MapLib.tlb)和封送处理代码。[/align]
[align=left] [/align]
[align=left]#include"olectl.h"[/align]
[align=left]import"oaidl.idl";[/align]
[align=left]import"ocidl.idl";[/align]
[align=left] [/align]
[align=left][[/align]
[align=left] uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),[/align]
[align=left] version(1.0),[/align]
[align=left] helpstring("MapLib 1.0类型库")[/align]
[align=left]][/align]
[align=left]library MapLibLib[/align]
[align=left]{[/align]
[align=left] importlib("stdole2.tlb");[/align]
[align=left] [/align]
[align=left] interface ICanvas; //预先声明[/align]
[align=left][/align]
[align=left] [[/align]
[align=left] object,[/align]
[align=left] uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),[/align]
[align=left] dual,[/align]
[align=left] nonextensible,[/align]
[align=left] helpstring("ICanvas接口"),[/align]
[align=left] pointer_default(unique)[/align]
[align=left] ][/align]
[align=left] interface ICanvas : IDispatch{[/align]
[align=left] [propput,bindable,requestedit,id(DISPID_BACKCOLOR)][/align]
[align=left] HRESULT BackColor([in]OLE_COLOR clr);[/align]
[align=left] [propget,bindable,requestedit,id(DISPID_BACKCOLOR)][/align]
[align=left] HRESULT BackColor([out,retval]OLE_COLOR* pclr);[/align]
[align=left] [propput,bindable,requestedit,id(DISPID_APPEARANCE)][/align]
[align=left] HRESULT Appearance([in]short nAppearance);[/align]
[align=left] [propget,bindable,requestedit,id(DISPID_APPEARANCE)][/align]
[align=left] HRESULT Appearance([out,retval]short* pnAppearance);[/align]
[align=left] };[/align]
[align=left] [/align]
[align=left] [[/align]
[align=left] uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),[/align]
[align=left] control,[/align]
[align=left] helpstring("Canvas Class")[/align]
[align=left] ][/align]
[align=left] coclass Canvas[/align]
[align=left] {[/align]
[align=left] [default]interface ICanvas;[/align]
[align=left] };[/align]
};

我只是把ICanvas接口部分移到了library MapLibLib{... ...}内,并增加了接口的预先声明:interface ICanvas; 我习惯把下面一句添加到CCanvas的构造方法里:
m_bWindowOnly = TRUE; //必须:总是创建自己的窗口
[align=left] [/align]
第三步 添加其他对象

这些对象都是ATL简单类型。名称为:Layers、Layer、Shapes、Shape。除了选择支持ISupportErrorInfo,其他都是采取默认设置。最后,手动修改类型库,修改后的IDL如下:
[align=left]// MapLib.idl : MapLib的IDL 源[/align]
[align=left]//[/align]
[align=left] [/align]
[align=left]//此文件将由MIDL 工具处理以[/align]
[align=left]//产生类型库(MapLib.tlb)和封送处理代码。[/align]
[align=left] [/align]
[align=left]#include"olectl.h"[/align]
[align=left]import"oaidl.idl";[/align]
[align=left]import"ocidl.idl";[/align]
[align=left] [/align]
[align=left] [/align]
[align=left][[/align]
[align=left] uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),[/align]
[align=left] version(1.0),[/align]
[align=left] helpstring("MapLib 1.0类型库")[/align]
[align=left]][/align]
[align=left]library MapLibLib[/align]
[align=left]{[/align]
[align=left] importlib("stdole2.tlb");[/align]
[align=left] [/align]
[align=left] //预先声明[/align]
[align=left] interface ICanvas;[/align]
[align=left] interface ILayers;[/align]
[align=left] interface ILayer;[/align]
[align=left] interface IShapes;[/align]
[align=left] interface IShape;[/align]
[align=left] [/align]
[align=left] [/align]
[align=left] [[/align]
[align=left] object,[/align]
[align=left] uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),[/align]
[align=left] dual,[/align]
[align=left] nonextensible,[/align]
[align=left] helpstring("ICanvas接口"),[/align]
[align=left] pointer_default(unique)[/align]
[align=left] ][/align]
[align=left] interface ICanvas : IDispatch{[/align]
[align=left] [propput,bindable,requestedit,id(DISPID_BACKCOLOR)][/align]
[align=left] HRESULT BackColor([in]OLE_COLOR clr);[/align]
[align=left] [propget,bindable,requestedit,id(DISPID_BACKCOLOR)][/align]
[align=left] HRESULT BackColor([out,retval]OLE_COLOR* pclr);[/align]
[align=left] [propput,bindable,requestedit,id(DISPID_APPEARANCE)][/align]
[align=left] HRESULT Appearance([in]short nAppearance);[/align]
[align=left] [propget,bindable,requestedit,id(DISPID_APPEARANCE)][/align]
[align=left] HRESULT Appearance([out,retval]short* pnAppearance);[/align]
[align=left] }; [/align]
[align=left] [[/align]
[align=left] object,[/align]
[align=left] uuid(874C2033-17F1-4534-BAB3-8F0367C45D14),[/align]
[align=left] dual,[/align]
[align=left] nonextensible,[/align]
[align=left] helpstring("ILayers接口"),[/align]
[align=left] pointer_default(unique)[/align]
[align=left] ][/align]
[align=left] interface ILayers : IDispatch{[/align]
[align=left] };[/align]
[align=left] [[/align]
[align=left] object,[/align]
[align=left] uuid(8D7872CF-9D97-4C4D-A26F-2BBEC59B7CB6),[/align]
[align=left] dual,[/align]
[align=left] nonextensible,[/align]
[align=left] helpstring("ILayer接口"),[/align]
[align=left] pointer_default(unique)[/align]
[align=left] ][/align]
[align=left] interface ILayer : IDispatch{[/align]
[align=left] };[/align]
[align=left] [[/align]
[align=left] object,[/align]
[align=left] uuid(E374F693-C4B3-49E7-948D-10C38C170DF7),[/align]
[align=left] dual,[/align]
[align=left] nonextensible,[/align]
[align=left] helpstring("IShapes接口"),[/align]
[align=left] pointer_default(unique)[/align]
[align=left] ][/align]
[align=left] interface IShapes : IDispatch{[/align]
[align=left] };[/align]
[align=left] [[/align]
[align=left] object,[/align]
[align=left] uuid(C576412D-69D0-42A8-AA96-FFD534472C0C),[/align]
[align=left] dual,[/align]
[align=left] nonextensible,[/align]
[align=left] helpstring("IShape接口"),[/align]
[align=left] pointer_default(unique)[/align]
[align=left] ][/align]
[align=left] interface IShape : IDispatch{[/align]
[align=left] };[/align]
[align=left] [/align]
[align=left] [[/align]
[align=left] uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),[/align]
[align=left] control,[/align]
[align=left] helpstring("Canvas Class")[/align]
[align=left] ][/align]
[align=left] coclass Canvas[/align]
[align=left] {[/align]
[align=left] [default]interface ICanvas;[/align]
[align=left] };[/align]
[align=left] [[/align]
[align=left] uuid(155C20C5-F3A0-47D7-AC5E-EB3F31EF3AD1),[/align]
[align=left] helpstring("Layers Class")[/align]
[align=left] ][/align]
[align=left] coclass Layers[/align]
[align=left] {[/align]
[align=left] [default]interface ILayers;[/align]
[align=left] };[/align]
[align=left] [[/align]
[align=left] uuid(F6543476-E5D9-4CC0-84B4-DD772D555686),[/align]
[align=left] helpstring("Layer Class")[/align]
[align=left] ][/align]
[align=left] coclass Layer[/align]
[align=left] {[/align]
[align=left] [default]interface ILayer;[/align]
[align=left] };[/align]
[align=left] [[/align]
[align=left] uuid(754A7947-8EDD-4B81-8522-9AF6B35F290D),[/align]
[align=left] helpstring("Shapes Class")[/align]
[align=left] ][/align]
[align=left] coclass Shapes[/align]
[align=left] {[/align]
[align=left] [default]interface IShapes;[/align]
[align=left] };[/align]
[align=left] [[/align]
[align=left] uuid(DE163AAE-62CF-46D7-956C-5884685DE634),[/align]
[align=left] helpstring("Shape Class")[/align]
[align=left] ][/align]
[align=left] coclass Shape[/align]
[align=left] {[/align]
[align=left] [default]interface IShape;[/align]
[align=left] };[/align]
[align=left]};[/align]

到目前为止,我们已经把需要的ATL类添加到类型库(MapLib.dll)中,编译全部通过。这样,基本的对象全都有了,对象模型初步建立OK。

三 实现集合对象

下面增加具体的实现代码,以把Layers和Shapes变成集合类(Collection)。

第一步 把下面的代码添加到stdafx.h中
[align=left]// stdafx.h :标准系统包含文件的包含文件,[/align]
[align=left]//或是经常使用但不常更改的[/align]
[align=left]//特定于项目的包含文件[/align]
[align=left] [/align]
[align=left](原有内容不动)[/align]
[align=left] [/align]
[align=left]//后添加的内容[/align]
[align=left]template <typename T>[/align]
[align=left]struct _CopyVariantFromAdaptItf {[/align]
[align=left] static HRESULT copy(VARIANT* p1,const CAdapt< CComPtr<T> >& p2) {[/align]
[align=left] HRESULT hr = p2.m_T->QueryInterface(IID_IDispatch, (void**)&p1->pdispVal);[/align]
[align=left] if (SUCCEEDED(hr)) {[/align]
[align=left] p1->vt = VT_DISPATCH;[/align]
[align=left] }[/align]
[align=left] else {[/align]
[align=left] hr = p2.m_T->QueryInterface(IID_IUnknown, (void**)&p1->punkVal);[/align]
[align=left] if( SUCCEEDED(hr) ) {[/align]
[align=left] p1->vt = VT_UNKNOWN;[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left][/align]
[align=left] return hr;[/align]
[align=left] }[/align]
[align=left][/align]
[align=left] staticvoid init(VARIANT* p) { VariantInit(p); }[/align]
[align=left] staticvoid destroy(VARIANT* p) { VariantClear(p); }[/align]
[align=left]};[/align]
[align=left][/align]
[align=left]template <typename T>[/align]
[align=left]struct _CopyItfFromAdaptItf {[/align]
[align=left] static HRESULT copy(T** p1,const CAdapt< CComPtr<T> >& p2) {[/align]
[align=left] if( *p1 = p2.m_T )return (*p1)->AddRef(), S_OK;[/align]
[align=left] return E_POINTER;[/align]
[align=left] }[/align]
[align=left][/align]
[align=left] staticvoid init(T** p) {}[/align]
[align=left] staticvoid destroy(T** p) {if( *p ) (*p)->Release(); }[/align]
};

第二步 把Layers变成集合对象

把下面的代码添加到Layers.h中,添加后的Layers.h为:
[align=left]// Layers.h : CLayers的声明[/align]
[align=left] [/align]
[align=left]#pragmaonce[/align]
[align=left]#include"resource.h" //主符号[/align]
[align=left] [/align]
[align=left]#include"MapLib.h"[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]#ifdefined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)[/align]
#error "Windows CE平台(如不提供完全DCOM 支持的Windows Mobile 平台)上无法正确支持单线程COM 对象。定义_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA 可强制ATL
支持创建单线程COM 对象实现并允许使用其单线程COM 对象实现。rgs 文件中的线程模型已被设置为“Free”,原因是该模型是非DCOM Windows CE 平台支持的唯一线程模型。"
[align=left]#endif[/align]
[align=left] [/align]
[align=left]//下面加粗的文字是我添加的代码[/align]
[align=left]#include"Layer.h"[/align]
[align=left][/align]
[align=left]typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,[/align]
[align=left]_CopyVariantFromAdaptItf<ILayer>,[/align]
[align=left]list< CAdapt< CComPtr<ILayer> > > >[/align]
[align=left]CComEnumVariantOnListOfLayers;[/align]
[align=left][/align]
[align=left]typedef ICollectionOnSTLImpl<IDispatchImpl<ILayers, &IID_ILayers>,[/align]
[align=left]list< CAdapt< CComPtr<ILayer> > >,[/align]
[align=left]ILayer*,[/align]
[align=left]_CopyItfFromAdaptItf<ILayer>,[/align]
[align=left]CComEnumVariantOnListOfLayers>[/align]
[align=left]ILayerCollImpl;[/align]
[align=left][/align]
[align=left]// CLayers[/align]
[align=left][/align]
[align=left]class ATL_NO_VTABLE CLayers :[/align]
[align=left] public CComObjectRootEx<CComSingleThreadModel>,[/align]
[align=left] public CComCoClass<CLayers>, // non-createable,去掉注册表项[/align]
[align=left] public ISupportErrorInfo,[/align]
[align=left] public ILayerCollImpl[/align]
[align=left]{[/align]
[align=left]public:[/align]
[align=left] CLayers()[/align]
[align=left] {[/align]
[align=left] }[/align]
[align=left][/align]
[align=left]//DECLARE_REGISTRY_RESOURCEID(IDR_LAYERS)[/align]
[align=left]DECLARE_NO_REGISTRY() // non-createable,去掉注册表项[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]//原来的代码被我注释掉[/align]
[align=left]// CHEUNGMINE:[/align]
[align=left]// CHEUNGMINE: // CLayers[/align]
[align=left] [/align]
[align=left]// CHEUNGMINE: class ATL_NO_VTABLE CLayers :[/align]
[align=left]// CHEUNGMINE: public CComObjectRootEx<CComSingleThreadModel>,[/align]
[align=left]// CHEUNGMINE: public CComCoClass<CLayers, &CLSID_Layers>,[/align]
[align=left]// CHEUNGMINE: public ISupportErrorInfo,[/align]
[align=left]// CHEUNGMINE: public IDispatchImpl<ILayers, &IID_ILayers, &LIBID_MapLibLib, /*wMajor =*/ 1, /*wMinor =*/ 0>[/align]
[align=left]// CHEUNGMINE: {[/align]
[align=left]// CHEUNGMINE: public:[/align]
[align=left]// CHEUNGMINE: CLayers()[/align]
[align=left]// CHEUNGMINE: {[/align]
[align=left]// CHEUNGMINE: }[/align]
[align=left] [/align]
[align=left]// CHEUNGMINE: DECLARE_REGISTRY_RESOURCEID(IDR_LAYERS)[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]BEGIN_COM_MAP(CLayers)[/align]
[align=left] COM_INTERFACE_ENTRY(ILayers)[/align]
[align=left] COM_INTERFACE_ENTRY(IDispatch)[/align]
[align=left] COM_INTERFACE_ENTRY(ISupportErrorInfo)[/align]
[align=left]END_COM_MAP()[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]// ISupportsErrorInfo[/align]
[align=left] STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);[/align]
[align=left] [/align]
[align=left] [/align]
[align=left] DECLARE_PROTECT_FINAL_CONSTRUCT()[/align]
[align=left] [/align]
[align=left] HRESULT FinalConstruct()[/align]
[align=left] {[/align]
[align=left] return S_OK;[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] void FinalRelease()[/align]
[align=left] {[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left]public:[/align]
[align=left] [/align]
[align=left]};[/align]
[align=left] [/align]
OBJECT_ENTRY_AUTO(__uuidof(Layers), CLayers)

然后编译,出现一堆的错误。首先在文件“stdafx.h”中的适当位置加入使用STL的语句,如下:
[align=left]// Standard C++ STL supports[/align]
[align=left]#include<stack>[/align]
[align=left]#include<vector>[/align]
[align=left]#include<list>[/align]
[align=left]#include<map>[/align]
[align=left]#include<string>[/align]
[align=left]#include<algorithm>[/align]
[align=left]#include<functional>[/align]
usingnamespace std;

然后,重新编译,全部OK。接下来,修改“MapLib.idl”文件的ILayers部分,使其如下所示:

[align=left]interface ILayers : IDispatch{[/align]
[propget,helpstring("Layer元素数目")]
HRESULT Count([out,retval]long *nItems);

[align=left] [id(DISPID_VALUE),propget,helpstring("取得指定索引的Layer元素。索引以为基数")][/align]
HRESULT Item([in]long index, [out,retval]
ILayer** ppRef);

[id(DISPID_NEWENUM),propget,hidden] HRESULT _NewEnum([out,retval]
IUnknown** ppEnum);

// [id(1),helpstring("向集合增加一个元素,返回引用")] HRESULT Add([out,retval] ILayer** ppRef);
};

第三步 把Shapes变成集合对象

修改后的完整的Shapes.h为:
[align=left]// Shapes.h : CShapes的声明[/align]
[align=left] [/align]
[align=left]#pragmaonce[/align]
[align=left]#include"resource.h" //主符号[/align]
[align=left] [/align]
[align=left]#include"MapLib.h"[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]#ifdefined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)[/align]
#error "Windows CE平台(如不提供完全DCOM 支持的Windows Mobile 平台)上无法正确支持单线程COM 对象。定义_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA 可强制ATL
支持创建单线程COM 对象实现并允许使用其单线程COM 对象实现。rgs 文件中的线程模型已被设置为“Free”,原因是该模型是非DCOM Windows CE 平台支持的唯一线程模型。"
[align=left]#endif[/align]
[align=left] [/align]
[align=left]#include"Shape.h"[/align]
[align=left][/align]
[align=left]typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,[/align]
[align=left]_CopyVariantFromAdaptItf<IShape>,[/align]
[align=left]vector< CAdapt< CComPtr<IShape> > > >[/align]
[align=left]CComEnumVariantOnArrayOfShapes;[/align]
[align=left][/align]
[align=left]typedef ICollectionOnSTLImpl<IDispatchImpl<IShapes, &IID_IShapes>,[/align]
[align=left]vector< CAdapt< CComPtr<IShape> > >,[/align]
[align=left]IShape*,[/align]
[align=left]_CopyItfFromAdaptItf<IShape>,[/align]
[align=left]CComEnumVariantOnArrayOfShapes>[/align]
[align=left]IShapesCollImpl;[/align]
[align=left] [/align]
[align=left]// CShapes[/align]
[align=left] [/align]
[align=left]class ATL_NO_VTABLE CShapes :[/align]
[align=left] public CComObjectRootEx<CComSingleThreadModel>,[/align]
public CComCoClass<CShapes>,// OLD: public CComCoClass<CShapes, &CLSID_Shapes>,

[align=left]// non-createable,去掉注册表项[/align]
[align=left] public ISupportErrorInfo,[/align]
[align=left] // public IDispatchImpl<IShapes, &IID_IShapes, &LIBID_MapLibLib, /*wMajor =*/ 1, /*wMinor =*/ 0>[/align]
[align=left] public IShapesCollImpl[/align]
[align=left]{[/align]
[align=left]public:[/align]
[align=left] CShapes()[/align]
[align=left] {[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left]// DECLARE_REGISTRY_RESOURCEID(IDR_SHAPES)[/align]
[align=left]DECLARE_NO_REGISTRY() // non-createable,去掉注册表项[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]BEGIN_COM_MAP(CShapes)[/align]
[align=left] COM_INTERFACE_ENTRY(IShapes)[/align]
[align=left] COM_INTERFACE_ENTRY(IDispatch)[/align]
[align=left] COM_INTERFACE_ENTRY(ISupportErrorInfo)[/align]
[align=left]END_COM_MAP()[/align]
[align=left] [/align]
[align=left]// ISupportsErrorInfo[/align]
[align=left] STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);[/align]
[align=left] [/align]
[align=left] [/align]
[align=left] DECLARE_PROTECT_FINAL_CONSTRUCT()[/align]
[align=left] [/align]
[align=left] HRESULT FinalConstruct()[/align]
[align=left] {[/align]
[align=left] return S_OK;[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] void FinalRelease()[/align]
[align=left] {[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left]public:[/align]
[align=left] [/align]
[align=left]};[/align]
[align=left] [/align]
OBJECT_ENTRY_AUTO(__uuidof(Shapes), CShapes)

接下来,修改“MapLib.idl”文件的IShapes部分,使其如下所示:
[align=left]interface IShapes : IDispatch{[/align]
[propget,helpstring("Shape元素数目")]
HRESULT Count([out,retval]long *nItems);
[align=left] [id(DISPID_VALUE),propget,helpstring("取得指定索引的Shape元素。索引以为基数")][/align]
HRESULT Item([in]long index, [out,retval]
IShape** ppRef);
[id(DISPID_NEWENUM),propget,hidden] HRESULT _NewEnum([out,retval]
IUnknown** ppEnum);
// [id(1),helpstring("向集合增加一个元素,返回引用")] HRESULT Add([out,retval] IShape** ppRef);
};

到此,整个集合的架构建立起来了。其中Layers集合是基于STL的list,而Shapes是基于STL的vector。用户可以根据自己要实现的模型的特点,选择使用适合的STL集合类。

四 添加实现代码

下面增加添加元素的方法,即去除前面IDL文件中的注释为:

[id(1),helpstring("向集合增加一个元素,返回引用")] HRESULT Add([out,retval] ILayer**
ppRef);

[id(1),helpstring("向集合增加一个元素,返回引用")] HRESULT Add([out,retval]
IShape** ppRef);


在Layers.h中,添加方法的实现代码如下:
[align=left]public:[/align]
[align=left] STDMETHODIMP Add(ILayer** ppRef)[/align]
[align=left] {[/align]
[align=left] CComPtr<ILayer> spObj;[/align]
[align=left] HRESULT hr = CLayer::CreateInstance(&spObj);[/align]
[align=left] if (SUCCEEDED(hr))[/align]
[align=left] {[/align]
[align=left] m_coll.push_back(spObj);[/align]
[align=left] return spObj.CopyTo(ppRef);[/align]
[align=left] }[/align]
[align=left] return hr;[/align]
[align=left] }[/align]

在Shapes.h中,添加方法的实现代码如下:
[align=left]public:[/align]
[align=left] STDMETHODIMP Add(IShape** ppRef)[/align]
[align=left] {[/align]
[align=left] CComPtr<IShape> spObj;[/align]
[align=left] HRESULT hr = CShape::CreateInstance(&spObj);[/align]
[align=left] if (SUCCEEDED(hr))[/align]
[align=left] {[/align]
[align=left] m_coll.push_back(spObj);[/align]
[align=left] return spObj.CopyTo(ppRef);[/align]
[align=left] }[/align]
[align=left] return hr;[/align]
}

现在,我们的集合对象已经基本好了。同时,通过一系列的改动,我们把Layers和Shapes指定为不可创建的对象,所以,我们可以把它们的注册表条目彻底删除:把Layers.rgs和Shapes.rgs大胆地彻底消灭掉。当你消灭了它们,编译会提示2条错误,双击错误,在文件MapLib.rc中,把下面的条目删除:
[align=left]IDR_LAYERS REGISTRY "Layers.rgs"[/align]
[align=left]IDR_SHAPES REGISTRY "Shapes.rgs"[/align]
[align=left][/align]
[align=left]特别值得注意:[/align]
依照此方法,你可以删除任何不需要独立创建的COM对象。比如本例中的Shape和Layer对象也不需要在注册表里注册,所以你也可以仿照Layers和Shapes的处理方式,改变向导生成的代码,删除它们的条目。如果你仍然想单独创建它们,你可以在根对象接口(这里是ICanvas)中添加创建的方法,如CreateObject,而在CreateObject方法内部实现上,采用C++创建对象,而不是使用COM类厂(需要通过注册表的GUID机制创建对象,速度比用C++创建对象慢了不知道多少倍。尤其在脚本中创建对象,使用new
ActiveXObject方法是异常慢的)这样做的好处是,你在注册表中只需要留有根对象条目,简洁的多拉。

我们的Layers是通过Canvas得到的,因此,添加属性到ICanvas中:
[align=left]// MapLib.idl : MapLib的IDL 源[/align]
[align=left]...[/align]
interface ICanvas : IDispatch{
......
[propget,id(1),helpstring("得到图层对象的引用")]
HRESULT Layers([out, retval] ILayers** ppRef);
// [id(2),helpstring("使用C++创建对象的例子")]
HRESULT CreateObject([in] BSTR bstrObjName, [out,retval] IDispatch** ppOutObj);

}

添加实现代码到Canvas.h中,你只需注意加粗的文字:
[align=left]#include"Layers.h"[/align]
[align=left] [/align]
[align=left]// CCanvas[/align]
[align=left]class ATL_NO_VTABLE CCanvas :[/align]
[align=left] public CComObjectRootEx<CComSingleThreadModel>,[/align]
[align=left] ...[/align]
[align=left]{[/align]
[align=left]public:[/align]
[align=left] CComPtr<ILayers> m_spLayers;[/align]
[align=left] [/align]
[align=left] CCanvas()[/align]
[align=left] {[/align]
[align=left]m_bWindowOnly = TRUE; //必须:总是创建自己的窗口[/align]
}

...

[align=left]HRESULT FinalConstruct()[/align]
[align=left] {[/align]
[align=left] return CLayers::CreateInstance(&m_spLayers);[/align]
[align=left]// return S_OK;[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] void FinalRelease()[/align]
[align=left] {[/align]
[align=left] }[/align]
[align=left]public:[/align]
[align=left] STDMETHODIMP CCanvas::get_Layers(ILayers** ppRef)[/align]
[align=left] {[/align]
[align=left] return m_spLayers.CopyTo(ppRef);[/align]
}
[align=left] [/align]
[align=left]//此方法在Canvas.cpp中实现,以避免交叉引用头文件[/align]
[align=left]// STDMETHOD(CreateObject)(BSTR bstrObjName, IDispatch** ppOutObj);[/align]

};

在Canvas.cpp中,CreateObject可以如下实现:
[align=left]// Canvas.cpp : CCanvas的实现[/align]
[align=left]#include"stdafx.h"[/align]
[align=left]#include"Canvas.h"[/align]
[align=left] [/align]
[align=left]// CCanvas[/align]
[align=left]#include"Layers.h"[/align]
[align=left]#include"Layer.h"[/align]
[align=left]#include"Shapes.h"[/align]
[align=left]#include"Shape.h"[/align]
[align=left]/*[/align]
[align=left]STDMETHODIMP CCanvas::CreateObject(BSTR bstrObjName, IDispatch** ppOutObj)[/align]
[align=left]{[/align]
[align=left] if (!bstrObjName || !ppOutObj)[/align]
[align=left] return E_POINTER;[/align]
[align=left] [/align]
[align=left] if (wcsicmp(bstrObjName, "layers")==0)[/align]
[align=left] {[/align]
[align=left] CComPtr<ILayers> spOut;[/align]
[align=left] CLayers::CreateInstance(&spOut);[/align]
[align=left] if(spOut)[/align]
[align=left] return spOut->QueryInterface(IID_IDispatch, (void**)ppOutObj);[/align]
[align=left] }[/align]
[align=left] else if (wcsicmp(bstrObjName, "layer")==0)[/align]
[align=left] { [/align]
[align=left] CComPtr<ILayer> spOut;[/align]
[align=left] CLayer::CreateInstance(&spOut);[/align]
[align=left] if(spOut)[/align]
[align=left] return spOut->QueryInterface(IID_IDispatch, (void**)ppOutObj);[/align]
[align=left] }[/align]
[align=left] else if(wcsicmp(bstrObjName, "shapes")==0)[/align]
[align=left] { [/align]
[align=left] CComPtr<IShapes> spOut;[/align]
[align=left] CShapes::CreateInstance(&spOut);[/align]
[align=left] if(spOut)[/align]
[align=left] return spOut->QueryInterface(IID_IDispatch, (void**)ppOutObj);[/align]
[align=left] }[/align]
[align=left] else if(wcsicmp(bstrObjName, "shape")==0)[/align]
[align=left] {[/align]
[align=left] CComPtr<IShape> spOut;[/align]
[align=left] CShape::CreateInstance(&spOut);[/align]
[align=left] if(spOut)[/align]
[align=left] return spOut->QueryInterface(IID_IDispatch, (void**)ppOutObj);[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] return E_INVALIDARG; [/align]
}
*/
Canvas.cpp的CreateObject例子使用简单的字符串比较,实际应用中,可以采用hash map等加快对象查找的速度。所以,COM对象有时候也要用其它方法创建为好!

同样的方法,给Layer对象添加属性,以得到 Shapes集合属性:
[align=left]// MapLib.idl : MapLib的IDL 源[/align]
[align=left]...[/align]

[align=left]interface ILayer : IDispatch{[/align]
[propget,id(1),helpstring("得到图形集合对象的引用")]
HRESULT Shapes([out, retval] IShapes** ppRef);
};

添加实现代码到Layer.h中,你只需注意加粗的文字:
[align=left]// Layer.h : CLayer的声明[/align]
[align=left]...[/align]
[align=left]#include"Shapes.h"[/align]
[align=left] [/align]
[align=left]// CLayer[/align]
[align=left]class ATL_NO_VTABLE CLayer :[/align]
[align=left]...[/align]
[align=left]{[/align]
[align=left]public:[/align]
[align=left] CComPtr<IShapes> m_spShapes;[/align]
[align=left] [/align]
[align=left] CLayer()[/align]
[align=left] {[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left]...[/align]
[align=left] [/align]
[align=left] HRESULT FinalConstruct()[/align]
[align=left] {[/align]
[align=left] return CShapes::CreateInstance(&m_spShapes);[/align]
[align=left] // return S_OK;[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] void FinalRelease()[/align]
[align=left] {[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left]public:[/align]
[align=left] STDMETHODIMP get_Shapes(IShapes** ppRef)[/align]
[align=left] {[/align]
[align=left] return m_spShapes.CopyTo(ppRef);[/align]
[align=left] }[/align]
[align=left]};[/align]

现在,你的对象模型就建好了。你可以从Canvas根对象得到图层集合(Layers)对象,你还可以向Layers中添加图层。你可以根据索引(目前是以1为基数)得到图层对象(Layer)的引用。从Layer对象得到图形集合对象(Shapes),并进一步操纵Shapes。

五 改变默认的索引基数和修改atlcom.h

最后要把以1为基数的索引,改为以0为基数的索引。这需要修改系统的头文件:atlcom.h。首先找到它,复制一份,更名为atlcom0.h,需要修改的地方我用粗体做了标记:
// atlcom.h--->atlcom0.h
[align=left] [/align]
template <class T,class CollType,class
ItemType,class CopyItem,class EnumType>
[align=left]class ICollectionOnSTLImpl :public T[/align]
[align=left]{[/align]
[align=left]public:[/align]
[align=left] STDMETHOD(get_Count)(long* pcount)[/align]
[align=left] {[/align]
[align=left] if (pcount == NULL)[/align]
[align=left] return E_POINTER;[/align]
[align=left] ATLASSUME(m_coll.size()<=LONG_MAX);[/align]
[align=left] [/align]
[align=left] *pcount = (long)m_coll.size();[/align]
[align=left] [/align]
[align=left] return S_OK;[/align]
[align=left] }[/align]
[align=left] STDMETHOD(get_Item)(long Index, ItemType* pvar)[/align]
[align=left] {[/align]
[align=left]#ifdef ITEM_INDEX_0_BASED[/align]
[align=left] //Index is 0-based[/align]
[align=left] if (pvar == NULL)[/align]
[align=left] return E_POINTER;[/align]
[align=left] if (Index < 0)[/align]
[align=left] return E_INVALIDARG;[/align]
[align=left] HRESULT hr = E_FAIL;[/align]
[align=left] CollType::iterator iter = m_coll.begin();[/align]
[align=left] while (iter != m_coll.end() && Index > 0)[/align]
[align=left] {[/align]
[align=left] iter++;[/align]
[align=left] Index--;[/align]
[align=left] }[/align]
[align=left] if (iter != m_coll.end())[/align]
[align=left] //hr = CopyItem::copy(pvar, &*iter);[/align]
[align=left] hr = CopyItem::copy(pvar, *iter); // CL2[/align]
[align=left] return hr;[/align]
[align=left]#else[/align]
[align=left]//Index is 1-based[/align]
[align=left] if (pvar == NULL)[/align]
[align=left] return E_POINTER;[/align]
[align=left] if (Index < 1)[/align]
[align=left] return E_INVALIDARG;[/align]
[align=left] HRESULT hr = E_FAIL;[/align]
[align=left] Index--;[/align]
[align=left] CollType::const_iterator iter = m_coll.begin();[/align]
[align=left] while (iter != m_coll.end() && Index > 0)[/align]
[align=left] {[/align]
[align=left] iter++;[/align]
[align=left] Index--;[/align]
[align=left] }[/align]
[align=left] if (iter != m_coll.end())[/align]
[align=left] //hr = CopyItem::copy(pvar, &*iter);[/align]
hr = CopyItem::copy(pvar, *iter);
// CL2
[align=left] return hr;[/align]
[align=left]#endif [/align]
}

原来的atlcom.h中有个小BUG,导致VS2005编译无法通过。在atlcom0.h文件中找到下面的函数:
STDMETHODIMP IEnumOnSTLImpl<Base, piid, T, Copy, CollType>::Next
更正之(只需要改变粗体的地方,一句话而已 ),即将
hr = Copy::copy(pelt, &*m_iter);
改为
hr = Copy::copy(pelt, *m_iter);
改过之后的完整的函数如下:
template <class Base,const IID* piid,class
T,class Copy,class CollType>
[align=left]STDMETHODIMP IEnumOnSTLImpl<Base, piid, T, Copy, CollType>::Next(ULONG celt, T* rgelt,[/align]
[align=left] ULONG* pceltFetched)[/align]
[align=left]{[/align]
[align=left] if (rgelt == NULL || (celt != 1 && pceltFetched == NULL))[/align]
[align=left] return E_POINTER;[/align]
[align=left] if (pceltFetched != NULL)[/align]
[align=left] *pceltFetched = 0;[/align]
[align=left] if (m_pcollection == NULL)[/align]
[align=left] return E_FAIL;[/align]
[align=left] [/align]
[align=left] ULONG nActual = 0;[/align]
[align=left] HRESULT hr = S_OK;[/align]
[align=left] T* pelt = rgelt;[/align]
[align=left] while (SUCCEEDED(hr) && m_iter != m_pcollection->end() && nActual < celt)[/align]
[align=left] {[/align]
[align=left] //hr = Copy::copy(pelt, &*m_iter);[/align]
[align=left] hr = Copy::copy(pelt, *m_iter);// cheungmine[/align]
[align=left] if (FAILED(hr))[/align]
[align=left] {[/align]
[align=left] while (rgelt < pelt)[/align]
[align=left] Copy::destroy(rgelt++);[/align]
[align=left] nActual = 0;[/align]
[align=left] }[/align]
[align=left] else[/align]
[align=left] {[/align]
[align=left] pelt++;[/align]
[align=left] m_iter++;[/align]
[align=left] nActual++;[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left] if (SUCCEEDED(hr))[/align]
[align=left] {[/align]
[align=left] if (pceltFetched)[/align]
[align=left] *pceltFetched = nActual;[/align]
[align=left] if (nActual < celt)[/align]
[align=left] hr = S_FALSE;[/align]
[align=left] }[/align]
[align=left] return hr;[/align]
[align=left]}[/align]

这样,在你所有包含atlcom.h的地方,用atlcom0.h替换,并在包含atlcom0.h前面,加入如下的宏:
[align=left]// stdafx.h[/align]
[align=left]...[/align]
[align=left]// NOT: #include <atlcom.h>[/align]
[align=left]#define ITEM_INDEX_0_BASED[/align]
#include"atlcom0.h" // 0-based index supports

六 结束语

好了,我要讲的内容都讲完了。你可以编写如下面的js脚本(Canvas.htm)使用这个对象模型:
[align=left]<HTML>[/align]
[align=left]<HEAD>[/align]
[align=left]<TITLE>cheungmine@gmail.com</TITLE>[/align]
[align=left]<scriptlanguage="javascript"type="text/javascript">[/align]
[align=left] function Button1_onclick() {[/align]
[align=left] lyrs = Canvas.Layers;[/align]
[align=left] lyr = lyrs.Add();[/align]
[align=left] alert(lyr);[/align]
[align=left] [/align]
[align=left] lyr.Shapes.Add();[/align]
[align=left] lyr.Shapes.Add();[/align]
[align=left] lyr.Shapes.Add();[/align]
[align=left] [/align]
[align=left] alert("Layers Count:"+lyrs.Count);[/align]
[align=left] alert("Shapes Count:"+lyr.Shapes.Count); [/align]
[align=left] }[/align]
[align=left] function Button2_onclick() {[/align]
[align=left] var cvs =new ActiveXObject("MapLib.Canvas");[/align]
[align=left] alert(cvs);[/align]
[align=left] [/align]
[align=left] cvs.Layers.Add();[/align]
[align=left] cvs.Layers.Add();[/align]
[align=left] cvs.Layers.Add();[/align]
[align=left] [/align]
[align=left] alert("Layers Count:"+cvs.Layers.Count); [/align]
[align=left]alert ( cvs.Layers.Item(0) ); //如果显示undefined,说明索引不是以-based [/align]
[align=left] }[/align]
[align=left] [/align]
[align=left]</script>[/align]
[align=left]</HEAD>[/align]
[align=left]<BODY>[/align]
[align=left]<OBJECTID="Canvas"CLASSID="CLSID:BC3D7FCC-C1AE-4476-A59C-431457A1173C"></OBJECT>[/align]
<inputid="Button1"
type="button"value="button"onclick="return Button1_onclick()"/>
<inputid="Button2"
type="button"value="button"onclick="return Button2_onclick()"/>
[align=left]</BODY>[/align]
</HTML>

以上内容,希望对朋友们有所帮助。这些内容看似简单,如果有兴趣,你就完整地做几遍,最后就变成你自己的知识了。我费了一天的时间把它整理出来,希望得到你们的批评指正!点击下面的链接可以得到本文的配套例子代码:
http://download.csdn.net/source/260939

http://blog.csdn.net/cheungmine/article/details/1818913
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: