您的位置:首页 > 其它

组件对象模型与ATL实现

2013-12-09 02:00 417 查看
应朋友之托,写这篇文章。
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文件,如下:
// MapLib.idl : MapLib
的IDL 源
[align=left]//[/align]
[align=left] [/align]
//
此文件将由MIDL 工具处理以
//
产生类型库(MapLib.tlb)和封送处理代码。
[align=left] [/align]
import
"oaidl.idl";
import
"ocidl.idl";
[align=left] [/align]
[align=left][[/align]

uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),

version(1.0),

helpstring("MapLib 1.0
类型库")
[align=left]][/align]
[align=left]library MapLibLib[/align]
[align=left]{[/align]

importlib("stdole2.tlb");
};

第二步 添加Canvas控件

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

按[完成]按钮。其实,上面的很多选项都可以以后添加,要求你熟悉ATL向导生成的代码。本文为简单起见,忽略了许多实际需要的因素。记住一点:在任何时候绝对不要使用属性化。这个在VS2003里作为默认选中的选项,在VC2005里被去掉了。首先,我不喜欢属性化。其次,属性化看似简单,但引入了另一个复杂性。微软在新版本里去掉默认属性化说明属性化的确有相当的负作用。
好拉,让我们再看看类型库(MapLib.idl):
// MapLib.idl : MapLib
的IDL 源
[align=left]//[/align]
[align=left] [/align]
//
此文件将由MIDL 工具处理以
//
产生类型库(MapLib.tlb)和封送处理代码。
[align=left] [/align]
#include
"olectl.h"
import
"oaidl.idl";
import
"ocidl.idl";
[align=left] [/align]
[align=left][[/align]

object,


uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),


dual,


nonextensible,


helpstring("ICanvas
接口"),


pointer_default(unique)

[align=left]][/align]
[align=left]interface ICanvas : IDispatch{[/align]
[propput,
bindable, requestedit,
id(DISPID_BACKCOLOR)]

[align=left] HRESULT BackColor([in]OLE_COLOR clr);[/align]
[propget,
bindable, requestedit,
id(DISPID_BACKCOLOR)]

[align=left] HRESULT BackColor([out,retval]OLE_COLOR* pclr);[/align]
[propput,
bindable, requestedit,
id(DISPID_APPEARANCE)]

[align=left] HRESULT Appearance([in]short nAppearance);[/align]
[propget,
bindable, requestedit,
id(DISPID_APPEARANCE)]

HRESULT Appearance([out,
retval]short* pnAppearance);

[align=left]};[/align]
[align=left] [/align]
[align=left][[/align]

uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),

version(1.0),

helpstring("MapLib 1.0
类型库")
[align=left]][/align]
[align=left]library MapLibLib[/align]
[align=left]{[/align]

importlib("stdole2.tlb");
[align=left] [[/align]

uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),

control,

helpstring("Canvas Class")
[align=left] ][/align]

coclass Canvas
[align=left] {[/align]
[default]
interface ICanvas;
[align=left] };[/align]
};

遗憾的是,这不是我需要的结果,我们需要修改类型库文件,修改后的结果如下:
// MapLib.idl : MapLib
的IDL 源
[align=left]//[/align]
[align=left] [/align]
//
此文件将由MIDL 工具处理以
//
产生类型库(MapLib.tlb)和封送处理代码。
[align=left] [/align]
#include
"olectl.h"
import
"oaidl.idl";
import
"ocidl.idl";
[align=left] [/align]
[align=left][[/align]

uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),

version(1.0),

helpstring("MapLib 1.0
类型库")
[align=left]][/align]
[align=left]library MapLibLib[/align]
[align=left]{[/align]

importlib("stdole2.tlb");
[align=left] [/align]

interface ICanvas; //
预先声明
[align=left][/align]
[align=left] [[/align]

object,


uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),


dual,


nonextensible,


helpstring("ICanvas
接口"),


pointer_default(unique)

[align=left] ][/align]

interface ICanvas : IDispatch{

[propput,
bindable, requestedit,
id(DISPID_BACKCOLOR)]

[align=left] HRESULT BackColor([in]OLE_COLOR clr);[/align]
[propget,
bindable, requestedit,
id(DISPID_BACKCOLOR)]

[align=left] HRESULT BackColor([out,retval]OLE_COLOR* pclr);[/align]
[propput,
bindable, requestedit,
id(DISPID_APPEARANCE)]

[align=left] HRESULT Appearance([in]short nAppearance);[/align]
[propget,
bindable, requestedit,
id(DISPID_APPEARANCE)]

HRESULT Appearance([out,
retval]short* pnAppearance);

[align=left] };[/align]
[align=left] [/align]
[align=left] [[/align]

uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),

control,

helpstring("Canvas Class")
[align=left] ][/align]

coclass Canvas
[align=left] {[/align]
[default]
interface ICanvas;
[align=left] };[/align]
};

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

这些对象都是ATL简单类型。名称为:Layers、Layer、Shapes、Shape。除了选择支持ISupportErrorInfo,其他都是采取默认设置。最后,手动修改类型库,修改后的IDL如下:
// MapLib.idl : MapLib
的IDL 源
[align=left]//[/align]
[align=left] [/align]
//
此文件将由MIDL 工具处理以
//
产生类型库(MapLib.tlb)和封送处理代码。
[align=left] [/align]
#include
"olectl.h"
import
"oaidl.idl";
import
"ocidl.idl";
[align=left] [/align]
[align=left] [/align]
[align=left][[/align]

uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),

version(1.0),

helpstring("MapLib 1.0
类型库")
[align=left]][/align]
[align=left]library MapLibLib[/align]
[align=left]{[/align]

importlib("stdole2.tlb");
[align=left] [/align]

// 预先声明

interface ICanvas;

interface ILayers;

interface ILayer;

interface IShapes;

interface IShape;
[align=left] [/align]
[align=left] [/align]
[align=left] [[/align]

object,

uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),

dual,

nonextensible,

helpstring("ICanvas
接口"),

pointer_default(unique)
[align=left] ][/align]

interface ICanvas : IDispatch{
[propput,
bindable, requestedit,
id(DISPID_BACKCOLOR)]
[align=left] HRESULT BackColor([in]OLE_COLOR clr);[/align]
[propget,
bindable, requestedit,
id(DISPID_BACKCOLOR)]
[align=left] HRESULT BackColor([out,retval]OLE_COLOR* pclr);[/align]
[propput,
bindable, requestedit,
id(DISPID_APPEARANCE)]
[align=left] HRESULT Appearance([in]short nAppearance);[/align]
[propget,
bindable, requestedit,
id(DISPID_APPEARANCE)]
HRESULT Appearance([out,
retval]short* pnAppearance);
};

[align=left] [[/align]

object,

uuid(874C2033-17F1-4534-BAB3-8F0367C45D14),

dual,

nonextensible,

helpstring("ILayers
接口"),

pointer_default(unique)
[align=left] ][/align]

interface ILayers : IDispatch{
[align=left] };[/align]
[align=left] [[/align]

object,

uuid(8D7872CF-9D97-4C4D-A26F-2BBEC59B7CB6),

dual,

nonextensible,

helpstring("ILayer
接口"),

pointer_default(unique)
[align=left] ][/align]

interface ILayer : IDispatch{
[align=left] };[/align]
[align=left] [[/align]

object,

uuid(E374F693-C4B3-49E7-948D-10C38C170DF7),

dual,

nonextensible,

helpstring("IShapes
接口"),

pointer_default(unique)
[align=left] ][/align]

interface IShapes : IDispatch{
[align=left] };[/align]
[align=left] [[/align]

object,

uuid(C576412D-69D0-42A8-AA96-FFD534472C0C),

dual,

nonextensible,

helpstring("IShape
接口"),

pointer_default(unique)
[align=left] ][/align]

interface IShape : IDispatch{
[align=left] };[/align]
[align=left] [/align]
[align=left] [[/align]

uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),

control,

helpstring("Canvas Class")
[align=left] ][/align]

coclass Canvas
[align=left] {[/align]
[default]
interface ICanvas;
[align=left] };[/align]
[align=left] [[/align]

uuid(155C20C5-F3A0-47D7-AC5E-EB3F31EF3AD1),

helpstring("Layers Class")
[align=left] ][/align]

coclass Layers
[align=left] {[/align]
[default]
interface ILayers;
[align=left] };[/align]
[align=left] [[/align]

uuid(F6543476-E5D9-4CC0-84B4-DD772D555686),

helpstring("Layer Class")
[align=left] ][/align]

coclass Layer
[align=left] {[/align]
[default]
interface ILayer;
[align=left] };[/align]
[align=left] [[/align]

uuid(754A7947-8EDD-4B81-8522-9AF6B35F290D),

helpstring("Shapes Class")
[align=left] ][/align]

coclass Shapes
[align=left] {[/align]
[default]
interface IShapes;
[align=left] };[/align]
[align=left] [[/align]

uuid(DE163AAE-62CF-46D7-956C-5884685DE634),

helpstring("Shape Class")
[align=left] ][/align]

coclass Shape
[align=left] {[/align]
[default]
interface IShape;
[align=left] };[/align]
[align=left]};[/align]

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

三 实现集合对象

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

第一步 把下面的代码添加到stdafx.h中
// stdafx.h :
标准系统包含文件的包含文件,
//
或是经常使用但不常更改的
//
特定于项目的包含文件
[align=left] [/align]
[align=left](原有内容不动)[/align]
[align=left] [/align]
//
后添加的内容

[align=left]template <typename T>[/align]
[align=left]struct _CopyVariantFromAdaptItf {[/align]
static HRESULT copy(VARIANT* p1,
const CAdapt< CComPtr<T> >& p2) {

[align=left] HRESULT hr = p2.m_T->QueryInterface(IID_IDispatch, (void**)&p1->pdispVal);[/align]

if (SUCCEEDED(hr)) {

[align=left] p1->vt = VT_DISPATCH;[/align]
[align=left] }[/align]

else {

[align=left] hr = p2.m_T->QueryInterface(IID_IUnknown, (void**)&p1->punkVal);[/align]

if( SUCCEEDED(hr) ) {

[align=left] p1->vt = VT_UNKNOWN;[/align]
[align=left] }[/align]
[align=left] }[/align]
[align=left][/align]

return hr;

[align=left] }[/align]
[align=left][/align]
static
void init(VARIANT* p) { VariantInit(p); }

static
void destroy(VARIANT* p) { VariantClear(p); }

[align=left]};[/align]
[align=left][/align]
[align=left]template <typename T>[/align]
[align=left]struct _CopyItfFromAdaptItf {[/align]

static HRESULT copy(T** p1,
const CAdapt< CComPtr<T> >& p2) {


if( *p1 = p2.m_T ) return (*p1)->AddRef(), S_OK;


return E_POINTER;

[align=left] }[/align]
[align=left][/align]
static
void init(T** p) {}

static
void destroy(T** p) { if( *p ) (*p)->Release(); }

};

第二步 把Layers变成集合对象

把下面的代码添加到Layers.h中,添加后的Layers.h为:
// Layers.h : CLayers
的声明
[align=left] [/align]
#pragma
once
#include
"resource.h" //
主符号
[align=left] [/align]
#include
"MapLib.h"
[align=left] [/align]
[align=left] [/align]
#if
defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#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]
//
下面加粗的文字是我添加的代码
#include
"Layer.h"

[align=left][/align]
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,

[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]

public CComObjectRootEx<CComSingleThreadModel>,


public CComCoClass<CLayers>,
// non-createable,去掉注册表项


public ISupportErrorInfo,


public ILayerCollImpl

[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]
DECLARE_NO_REGISTRY()
// non-createable,去掉注册表项

[align=left] [/align]
[align=left] [/align]
//
原来的代码被我注释掉
// CHEUNGMINE:

[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]

return S_OK;
[align=left] }[/align]
[align=left] [/align]

void FinalRelease()
[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]
#include
<stack>

#include
<vector>

#include
<list>

#include
<map>

#include
<string>

#include
<algorithm>

#include
<functional>

using
namespace std;


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

[align=left]interface ILayers : IDispatch{[/align]

[propget,helpstring("Layer元素数目")] HRESULT Count([out,retval]
long *nItems);

[id(DISPID_VALUE),propget,helpstring("取得指定索引的Layer元素。索引以为基数")]

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为:
// Shapes.h : CShapes
的声明
[align=left] [/align]
#pragma
once
#include
"resource.h" //
主符号
[align=left] [/align]
#include
"MapLib.h"
[align=left] [/align]
[align=left] [/align]
#if
defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#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]
#include
"Shape.h"

[align=left][/align]
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,

[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]

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CShapes>,
// OLD: public CComCoClass<CShapes, &CLSID_Shapes>,
[align=left]// non-createable,去掉注册表项[/align]

public ISupportErrorInfo,

// public IDispatchImpl<IShapes, &IID_IShapes, &LIBID_MapLibLib, /*wMajor =*/ 1, /*wMinor =*/ 0>

public IShapesCollImpl
[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]
DECLARE_NO_REGISTRY()
// non-createable,去掉注册表项
[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]

return S_OK;
[align=left] }[/align]
[align=left] [/align]

void FinalRelease()
[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);
[id(DISPID_VALUE),propget,helpstring("取得指定索引的Shape元素。索引以为基数")]

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]

if (SUCCEEDED(hr))

[align=left] {[/align]
[align=left] m_coll.push_back(spObj);[/align]

return spObj.CopyTo(ppRef);

[align=left] }[/align]

return hr;

[align=left] }[/align]

在Shapes.h中,添加方法的实现代码如下:
[align=left]public:[/align]

STDMETHODIMP Add(IShape** ppRef)
[align=left] {[/align]
[align=left] CComPtr<IShape> spObj;[/align]
[align=left] HRESULT hr = CShape::CreateInstance(&spObj);[/align]

if (SUCCEEDED(hr))

[align=left] {[/align]
[align=left] m_coll.push_back(spObj);[/align]

return spObj.CopyTo(ppRef);

[align=left] }[/align]

return hr;

}

现在,我们的集合对象已经基本好了。同时,通过一系列的改动,我们把Layers和Shapes指定为不可创建的对象,所以,我们可以把它们的注册表条目彻底删除:把Layers.rgs和Shapes.rgs大胆地彻底消灭掉。当你消灭了它们,编译会提示2条错误,双击错误,在文件MapLib.rc中,把下面的条目删除:
IDR_LAYERS REGISTRY
"Layers.rgs"

IDR_SHAPES REGISTRY
"Shapes.rgs"

[align=left][/align]
[align=left]特别值得注意:[/align]
依照此方法,你可以删除任何不需要独立创建的COM对象。比如本例中的Shape和Layer对象也不需要在注册表里注册,所以你也可以仿照Layers和Shapes的处理方式,改变向导生成的代码,删除它们的条目。如果你仍然想单独创建它们,你可以在根对象接口(这里是ICanvas)中添加创建的方法,如CreateObject,而在CreateObject方法内部实现上,采用C++创建对象,而不是使用COM类厂(需要通过注册表的GUID机制创建对象,速度比用C++创建对象慢了不知道多少倍。尤其在脚本中创建对象,使用new
ActiveXObject方法是异常慢的)这样做的好处是,你在注册表中只需要留有根对象条目,简洁的多拉。

我们的Layers是通过Canvas得到的,因此,添加属性到ICanvas中:
// MapLib.idl : MapLib
的IDL 源
[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中,你只需注意加粗的文字:
#include
"Layers.h"

[align=left] [/align]
[align=left]// CCanvas[/align]
[align=left]class ATL_NO_VTABLE CCanvas :[/align]

public CComObjectRootEx<CComSingleThreadModel>,

...
[align=left]{[/align]
[align=left]public:[/align]

CComPtr<ILayers> m_spLayers;
[align=left] [/align]
[align=left] CCanvas()[/align]
[align=left] {[/align]
m_bWindowOnly = TRUE;
// 必须:总是创建自己的窗口

}

...

[align=left]HRESULT FinalConstruct()[/align]
[align=left] {[/align]

return CLayers::CreateInstance(&m_spLayers);
// return S_OK;
[align=left] }[/align]
[align=left] [/align]

void FinalRelease()
[align=left] {[/align]
[align=left] }[/align]
[align=left]public:[/align]
[align=left] STDMETHODIMP CCanvas::get_Layers(ILayers** ppRef)[/align]
[align=left] {[/align]

return m_spLayers.CopyTo(ppRef);

}
[align=left] [/align]
//
此方法在Canvas.cpp中实现,以避免交叉引用头文件
[align=left]// STDMETHOD(CreateObject)(BSTR bstrObjName, IDispatch** ppOutObj);[/align]

};

在Canvas.cpp中,CreateObject可以如下实现:
// Canvas.cpp : CCanvas
的实现
#include
"stdafx.h"
#include
"Canvas.h"
[align=left] [/align]
[align=left]// CCanvas[/align]
#include
"Layers.h"
#include
"Layer.h"
#include
"Shapes.h"
#include
"Shape.h"
[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] 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] 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]

return E_INVALIDARG;

}
*/
Canvas.cpp的CreateObject例子使用简单的字符串比较,实际应用中,可以采用hash map等加快对象查找的速度。所以,COM对象有时候也要用其它方法创建为好!

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

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

添加实现代码到Layer.h中,你只需注意加粗的文字:
// Layer.h : CLayer
的声明
[align=left]...[/align]
#include
"Shapes.h"

[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]

CComPtr<IShapes> m_spShapes;
[align=left] [/align]
[align=left] CLayer()[/align]
[align=left] {[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left]...[/align]
[align=left] [/align]

HRESULT FinalConstruct()
[align=left] {[/align]

return CShapes::CreateInstance(&m_spShapes);


// return S_OK;

[align=left] }[/align]
[align=left] [/align]

void FinalRelease()
[align=left] {[/align]
[align=left] }[/align]
[align=left] [/align]
public:

[align=left] STDMETHODIMP get_Shapes(IShapes** ppRef)[/align]
[align=left] {[/align]

return m_spShapes.CopyTo(ppRef);

[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>
class ICollectionOnSTLImpl :
public T
[align=left]{[/align]
[align=left]public:[/align]
[align=left] STDMETHOD(get_Count)(long* pcount)[/align]
[align=left] {[/align]

if (pcount == NULL)

return E_POINTER;
[align=left] ATLASSUME(m_coll.size()<=LONG_MAX);[/align]
[align=left] [/align]
[align=left] *pcount = (long)m_coll.size();[/align]
[align=left] [/align]

return S_OK;
[align=left] }[/align]
[align=left] STDMETHOD(get_Item)(long Index, ItemType* pvar)[/align]
[align=left] {[/align]
[align=left]#ifdef ITEM_INDEX_0_BASED[/align]

//Index is 0-based


if (pvar == NULL)


return E_POINTER;


if (Index < 0)


return E_INVALIDARG;

[align=left] HRESULT hr = E_FAIL;[/align]
[align=left] CollType::iterator iter = m_coll.begin();[/align]

while (iter != m_coll.end() && Index > 0)

[align=left] {[/align]
[align=left] iter++;[/align]
[align=left] Index--;[/align]
[align=left] }[/align]

if (iter != m_coll.end())


//hr = CopyItem::copy(pvar, &*iter);

hr = CopyItem::copy(pvar, *iter);
// CL2


return hr;

[align=left]#else[/align]
//Index is 1-based

if (pvar == NULL)

return E_POINTER;

if (Index < 1)

return E_INVALIDARG;
[align=left] HRESULT hr = E_FAIL;[/align]
[align=left] Index--;[/align]
[align=left] CollType::const_iterator iter = m_coll.begin();[/align]

while (iter != m_coll.end() && Index > 0)
[align=left] {[/align]
[align=left] iter++;[/align]
[align=left] Index--;[/align]
[align=left] }[/align]

if (iter != m_coll.end())

//hr = CopyItem::copy(pvar, &*iter);

hr = CopyItem::copy(pvar, *iter);
// CL2

return hr;
#endif

}

原来的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]

if (rgelt == NULL || (celt != 1 && pceltFetched == NULL))

return E_POINTER;

if (pceltFetched != NULL)
[align=left] *pceltFetched = 0;[/align]

if (m_pcollection == NULL)

return E_FAIL;
[align=left] [/align]
[align=left] ULONG nActual = 0;[/align]
[align=left] HRESULT hr = S_OK;[/align]
[align=left] T* pelt = rgelt;[/align]

while (SUCCEEDED(hr) && m_iter != m_pcollection->end() && nActual < celt)
[align=left] {[/align]

// hr = Copy::copy(pelt, &*m_iter);

hr = Copy::copy(pelt, *m_iter); // cheungmine

if (FAILED(hr))
[align=left] {[/align]

while (rgelt < pelt)
[align=left] Copy::destroy(rgelt++);[/align]
[align=left] nActual = 0;[/align]
[align=left] }[/align]

else
[align=left] {[/align]
[align=left] pelt++;[/align]
[align=left] m_iter++;[/align]
[align=left] nActual++;[/align]
[align=left] }[/align]
[align=left] }[/align]

if (SUCCEEDED(hr))
[align=left] {[/align]

if (pceltFetched)
[align=left] *pceltFetched = nActual;[/align]

if (nActual < celt)
[align=left] hr = S_FALSE;[/align]
[align=left] }[/align]

return hr;
[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]
<script
language="javascript"
type="text/javascript">

function Button1_onclick() {
[align=left] lyrs = Canvas.Layers;[/align]
[align=left] lyr = lyrs.Add();[/align]
[align=left] alert(lyr);[/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]
alert("Shapes Count:"+lyr.Shapes.Count);

[align=left] }[/align]

function Button2_onclick() {

var cvs = new ActiveXObject("MapLib.Canvas");
[align=left] alert(cvs);[/align]

[align=left] cvs.Layers.Add();[/align]
[align=left] cvs.Layers.Add();[/align]
[align=left] cvs.Layers.Add();[/align]

[align=left] alert("Layers Count:"+cvs.Layers.Count); [/align]
alert ( cvs.Layers.Item(0) );
// 如果显示undefined,说明索引不是以-based

[align=left] }[/align]
[align=left] [/align]
[align=left]</script>[/align]
[align=left]</HEAD>[/align]
[align=left]<BODY>[/align]
<OBJECT
ID="Canvas"
CLASSID="CLSID:BC3D7FCC-C1AE-4476-A59C-431457A1173C"></OBJECT>

<input
id="Button1" type="button"
value="button"
onclick="return Button1_onclick()"
/>

<input
id="Button2" type="button"
value="button"
onclick="return Button2_onclick()"
/>
[align=left]</BODY>[/align]
</HTML>

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

[align=right]张亮[/align]
[align=right]2007-10-10初稿[/align]
[align=right]2007-10-11修改[/align]
[align=right]上海浦东[/align]
[align=right]转载请注明出处![/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: