组件对象模型与ATL实现
Component Object Model and Implementation with ATL
应朋友之托,写这篇文章。
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 源
//
//
此文件将由MIDL 工具处理以
//
产生类型库(MapLib.tlb)和封送处理代码。
import
"oaidl.idl";
import
"ocidl.idl";
[
uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),
version(1.0),
helpstring("MapLib 1.0
类型库"
)
]
library
MapLibLib
{
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 源
//
//
此文件将由MIDL 工具处理以
//
产生类型库(MapLib.tlb)和封送处理代码。
#include
"olectl.h"
import
"oaidl.idl";
import
"ocidl.idl";
[
object,
uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),
dual,
nonextensible,
helpstring("ICanvas接口"),
pointer_default(unique)
]
interface ICanvas : IDispatch{
[propput,bindable,requestedit,id(DISPID_BACKCOLOR)]
HRESULT BackColor([in]OLE_COLOR clr);
[propget,bindable,requestedit,id(DISPID_BACKCOLOR)]
HRESULT BackColor([out,retval]OLE_COLOR* pclr);
[propput,bindable,requestedit,id(DISPID_APPEARANCE)]
HRESULT Appearance([in]short nAppearance);
[propget,bindable,requestedit,id(DISPID_APPEARANCE)]
HRESULT Appearance([out,retval]short* pnAppearance);
};
[
uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),
version(1.0),
helpstring("MapLib 1.0
类型库"
)
]
library
MapLibLib
{
importlib("stdole2.tlb");
[
uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),
control,
helpstring("Canvas Class")
]
coclass Canvas
{
[default]interface ICanvas;
};
};
遗憾的是,这不是我需要的结果,我们需要修改类型库文件,修改后的结果如下:
// MapLib.idl : MapLib
的IDL 源
//
//
此文件将由MIDL 工具处理以
//
产生类型库(MapLib.tlb)和封送处理代码。
#include
"olectl.h"
import
"oaidl.idl";
import
"ocidl.idl";
[
uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),
version(1.0),
helpstring("MapLib 1.0
类型库"
)
]
library
MapLibLib
{
importlib("stdole2.tlb");
interface ICanvas; //
预先声明
[
object,
uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),
dual,
nonextensible,
helpstring("ICanvas接口"),
pointer_default(unique)
]
interface ICanvas : IDispatch{
[propput,bindable,requestedit,id(DISPID_BACKCOLOR)]
HRESULT BackColor([in]OLE_COLOR clr);
[propget,bindable,requestedit,id(DISPID_BACKCOLOR)]
HRESULT BackColor([out,retval]OLE_COLOR* pclr);
[propput,bindable,requestedit,id(DISPID_APPEARANCE)]
HRESULT Appearance([in]short nAppearance);
[propget,bindable,requestedit,id(DISPID_APPEARANCE)]
HRESULT Appearance([out,retval]short* pnAppearance);
};
[
uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),
control,
helpstring("Canvas Class")
]
coclass Canvas
{
[default]interface ICanvas;
};
};
我只是把ICanvas接口部分移到了library MapLibLib{... ...}内,并增加了接口的预先声明:interface ICanvas; 我习惯把下面一句添加到CCanvas的构造方法里:
m_bWindowOnly = TRUE; //
必须:总是创建自己的窗口
第三步 添加其他对象
这些对象都是ATL简单类型。名称为:Layers、Layer、Shapes、Shape。除了选择支持ISupportErrorInfo,其他都是采取默认设置。最后,手动修改类型库,修改后的IDL如下:
// MapLib.idl : MapLib
的IDL 源
//
//
此文件将由MIDL 工具处理以
//
产生类型库(MapLib.tlb)和封送处理代码。
#include
"olectl.h"
import
"oaidl.idl";
import
"ocidl.idl";
[
uuid(C8C046AB-3D44-45EF-B11C-C5822862049A),
version(1.0),
helpstring("MapLib 1.0
类型库"
)
]
library
MapLibLib
{
importlib("stdole2.tlb");
//
预先声明
interface ICanvas;
interface ILayers;
interface ILayer;
interface IShapes;
interface IShape;
[
object,
uuid(A74C036D-B046-4F53-B53A-F8EF611F576D),
dual,
nonextensible,
helpstring("ICanvas
接口"
),
pointer_default(unique)
]
interface ICanvas : IDispatch{
[propput,bindable,requestedit,id(DISPID_BACKCOLOR)]
HRESULT BackColor([in]OLE_COLOR clr);
[propget,bindable,requestedit,id(DISPID_BACKCOLOR)]
HRESULT BackColor([out,retval]OLE_COLOR* pclr);
[propput,bindable,requestedit,id(DISPID_APPEARANCE)]
HRESULT Appearance([in]short nAppearance);
[propget,bindable,requestedit,id(DISPID_APPEARANCE)]
HRESULT Appearance([out,retval]short* pnAppearance);
};
[
object,
uuid(874C2033-17F1-4534-BAB3-8F0367C45D14),
dual,
nonextensible,
helpstring("ILayers
接口"
),
pointer_default(unique)
]
interface ILayers : IDispatch{
};
[
object,
uuid(8D7872CF-9D97-4C4D-A26F-2BBEC59B7CB6),
dual,
nonextensible,
helpstring("ILayer
接口"
),
pointer_default(unique)
]
interface ILayer : IDispatch{
};
[
object,
uuid(E374F693-C4B3-49E7-948D-10C38C170DF7),
dual,
nonextensible,
helpstring("IShapes
接口"
),
pointer_default(unique)
]
interface IShapes : IDispatch{
};
[
object,
uuid(C576412D-69D0-42A8-AA96-FFD534472C0C),
dual,
nonextensible,
helpstring("IShape
接口"
),
pointer_default(unique)
]
interface IShape : IDispatch{
};
[
uuid(BC3D7FCC-C1AE-4476-A59C-431457A1173C),
control,
helpstring("Canvas Class")
]
coclass Canvas
{
[default]interface ICanvas;
};
[
uuid(155C20C5-F3A0-47D7-AC5E-EB3F31EF3AD1),
helpstring("Layers Class")
]
coclass Layers
{
[default]interface ILayers;
};
[
uuid(F6543476-E5D9-4CC0-84B4-DD772D555686),
helpstring("Layer Class")
]
coclass Layer
{
[default]interface ILayer;
};
[
uuid(754A7947-8EDD-4B81-8522-9AF6B35F290D),
helpstring("Shapes Class")
]
coclass Shapes
{
[default]interface IShapes;
};
[
uuid(DE163AAE-62CF-46D7-956C-5884685DE634),
helpstring("Shape Class")
]
coclass Shape
{
[default]interface IShape;
};
};
到目前为止,我们已经把需要的ATL类添加到类型库(MapLib.dll)中,编译全部通过。这样,基本的对象全都有了,对象模型初步建立OK。
三 实现集合对象
下面增加具体的实现代码,以把Layers和Shapes变成集合类(Collection)。
第一步 把下面的代码添加到stdafx.h中
// stdafx.h :
标准系统包含文件的包含文件,
//
或是经常使用但不常更改的
//
特定于项目的包含文件
(原有内容不动)
//后添加的内容
template <typename T>
struct _CopyVariantFromAdaptItf {
static HRESULT copy(VARIANT* p1,const CAdapt< CComPtr<T> >& p2) {
HRESULT hr = p2.m_T->QueryInterface(IID_IDispatch, (void**)&p1->pdispVal);
if (SUCCEEDED(hr)) {
p1->vt = VT_DISPATCH;
}
else {
hr = p2.m_T->QueryInterface(IID_IUnknown, (void**)&p1->punkVal);
if( SUCCEEDED(hr) ) {
p1->vt = VT_UNKNOWN;
}
}
return hr;
}
staticvoid init(VARIANT* p) { VariantInit(p); }
staticvoid destroy(VARIANT* p) { VariantClear(p); }
};
template <typename T>
struct _CopyItfFromAdaptItf {
static HRESULT copy(T** p1,const CAdapt< CComPtr<T> >& p2) {
if( *p1 = p2.m_T )return (*p1)->AddRef(), S_OK;
return E_POINTER;
}
staticvoid init(T** p) {}
staticvoid destroy(T** p) {if( *p ) (*p)->Release(); }
};
第二步 把Layers变成集合对象
把下面的代码添加到Layers.h中,添加后的Layers.h为:
// Layers.h : CLayers
的声明
#pragma
once
#include
"resource.h" //
主符号
#include
"MapLib.h"
#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 平台支持的唯一线程模型。"
#endif
//
下面加粗的文字是我添加的代码
#include"Layer.h"
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
_CopyVariantFromAdaptItf<ILayer>,
list< CAdapt< CComPtr<ILayer> > > >
CComEnumVariantOnListOfLayers;
typedef ICollectionOnSTLImpl<IDispatchImpl<ILayers, &IID_ILayers>,
list< CAdapt< CComPtr<ILayer> > >,
ILayer*,
_CopyItfFromAdaptItf<ILayer>,
CComEnumVariantOnListOfLayers>
ILayerCollImpl;
// CLayers
class ATL_NO_VTABLE CLayers :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CLayers>, // non-createable,去掉注册表项
public ISupportErrorInfo,
public ILayerCollImpl
{
public:
CLayers()
{
}
//DECLARE_REGISTRY_RESOURCEID(IDR_LAYERS)
DECLARE_NO_REGISTRY() // non-createable,去掉注册表项
//
原来的代码被我注释掉
// CHEUNGMINE:
// CHEUNGMINE: // CLayers
// CHEUNGMINE: class ATL_NO_VTABLE CLayers :
// CHEUNGMINE: public CComObjectRootEx<CComSingleThreadModel>,
// CHEUNGMINE: public CComCoClass<CLayers, &CLSID_Layers>,
// CHEUNGMINE: public ISupportErrorInfo,
// CHEUNGMINE: public IDispatchImpl<ILayers, &IID_ILayers, &LIBID_MapLibLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
// CHEUNGMINE: {
// CHEUNGMINE: public:
// CHEUNGMINE: CLayers()
// CHEUNGMINE: {
// CHEUNGMINE: }
// CHEUNGMINE: DECLARE_REGISTRY_RESOURCEID(IDR_LAYERS)
BEGIN_COM_MAP(CLayers)
COM_INTERFACE_ENTRY(ILayers)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public
:
};
OBJECT_ENTRY_AUTO(__uuidof(Layers), CLayers)
然后编译,出现一堆的错误。首先在文件“stdafx.h”中的适当位置加入使用STL的语句,如下:
// Standard C++ STL supports
#include<stack>
#include<vector>
#include<list>
#include<map>
#include<string>
#include<algorithm>
#include<functional>
usingnamespace std;
然后,重新编译,全部OK。接下来,修改“MapLib.idl”文件的ILayers部分,使其如下所示:
interface
ILayers : IDispatch{
[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
的声明
#pragma
once
#include
"resource.h" //
主符号
#include
"MapLib.h"
#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 平台支持的唯一线程模型。"
#endif
#include"Shape.h"
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
_CopyVariantFromAdaptItf<IShape>,
vector< CAdapt< CComPtr<IShape> > > >
CComEnumVariantOnArrayOfShapes;
typedef ICollectionOnSTLImpl<IDispatchImpl<IShapes, &IID_IShapes>,
vector< CAdapt< CComPtr<IShape> > >,
IShape*,
_CopyItfFromAdaptItf<IShape>,
CComEnumVariantOnArrayOfShapes>
IShapesCollImpl;
// CShapes
class
ATL_NO_VTABLE CShapes :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CShapes>,// OLD: public CComCoClass<CShapes, &CLSID_Shapes>,
// non-createable,去掉注册表项
public ISupportErrorInfo,
// public IDispatchImpl<IShapes, &IID_IShapes, &LIBID_MapLibLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
public IShapesCollImpl
{
public
:
CShapes()
{
}
// DECLARE_REGISTRY_RESOURCEID(IDR_SHAPES)
DECLARE_NO_REGISTRY()
// non-createable
,去掉注册表项
BEGIN_COM_MAP(CShapes)
COM_INTERFACE_ENTRY(IShapes)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public
:
};
OBJECT_ENTRY_AUTO(__uuidof(Shapes), CShapes)
接下来,修改“MapLib.idl”文件的IShapes部分,使其如下所示:
interface
IShapes : IDispatch{
[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中,添加方法的实现代码如下:
public
:
STDMETHODIMP Add(ILayer** ppRef)
{
CComPtr<ILayer> spObj;
HRESULT hr = CLayer::CreateInstance(&spObj);
if (SUCCEEDED(hr))
{
m_coll.push_back(spObj);
return spObj.CopyTo(ppRef);
}
return hr;
}
在Shapes.h中,添加方法的实现代码如下:
public
:
STDMETHODIMP Add(IShape** ppRef)
{
CComPtr<IShape> spObj;
HRESULT hr = CShape::CreateInstance(&spObj);
if (SUCCEEDED(hr))
{
m_coll.push_back(spObj);
return spObj.CopyTo(ppRef);
}
return hr;
}
现在,我们的集合对象已经基本好了。同时,通过一系列的改动,我们把Layers和Shapes指定为不可创建的对象,所以,我们可以把它们的注册表条目彻底删除:把Layers.rgs和Shapes.rgs大胆地彻底消灭掉。当你消灭了它们,编译会提示2条错误,双击错误,在文件MapLib.rc中,把下面的条目删除:
IDR_LAYERS REGISTRY "Layers.rgs"
IDR_SHAPES REGISTRY "Shapes.rgs"
特别值得注意:
依照此方法,你可以删除任何不需要独立创建的COM对象。比如本例中的Shape和Layer对象也不需要在注册表里注册,所以你也可以仿照Layers和Shapes的处理方式,改变向导生成的代码,删除它们的条目。如果你仍然想单独创建它们,你可以在根对象接口(这里是ICanvas)中添加创建的方法,如CreateObject,而在CreateObject方法内部实现上,采用C++创建对象,而不是使用COM类厂(需要通过注册表的GUID机制创建对象,速度比用C++创建对象慢了不知道多少倍。尤其在脚本中创建对象,使用new ActiveXObject方法是异常慢的)这样做的好处是,你在注册表中只需要留有根对象条目,简洁的多拉。
我们的Layers是通过Canvas得到的,因此,添加属性到ICanvas中:
// MapLib.idl : MapLib
的IDL 源
...
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"
// CCanvas
class
ATL_NO_VTABLE CCanvas :
public CComObjectRootEx<CComSingleThreadModel>,
...
{
public
:
CComPtr<ILayers> m_spLayers;
CCanvas()
{
m_bWindowOnly = TRUE; //必须:总是创建自己的窗口
}
...
HRESULT FinalConstruct()
{
return CLayers::CreateInstance(&m_spLayers);
// return S_OK;
}
void FinalRelease()
{
}
public:
STDMETHODIMP CCanvas::get_Layers(ILayers** ppRef)
{
return m_spLayers.CopyTo(ppRef);
}
//
此方法在Canvas.cpp中实现,以避免交叉引用头文件
// STDMETHOD(CreateObject)(BSTR bstrObjName, IDispatch** ppOutObj);
};
在Canvas.cpp中,CreateObject可以如下实现:
// Canvas.cpp : CCanvas
的实现
#include
"stdafx.h"
#include
"Canvas.h"
// CCanvas
#include
"Layers.h"
#include
"Layer.h"
#include
"Shapes.h"
#include
"Shape.h"
/*
STDMETHODIMP CCanvas::CreateObject(BSTR bstrObjName, IDispatch** ppOutObj)
{
if (!bstrObjName || !ppOutObj)
return E_POINTER;
if (wcsicmp(bstrObjName, "layers")==0)
{
CComPtr<ILayers> spOut;
CLayers::CreateInstance(&spOut);
if(spOut)
return spOut->QueryInterface(IID_IDispatch, (void**)ppOutObj);
}
else if (wcsicmp(bstrObjName, "layer")==0)
{
CComPtr<ILayer> spOut;
CLayer::CreateInstance(&spOut);
if(spOut)
return spOut->QueryInterface(IID_IDispatch, (void**)ppOutObj);
}
else if(wcsicmp(bstrObjName, "shapes")==0)
{
CComPtr<IShapes> spOut;
CShapes::CreateInstance(&spOut);
if(spOut)
return spOut->QueryInterface(IID_IDispatch, (void**)ppOutObj);
}
else if(wcsicmp(bstrObjName, "shape")==0)
{
CComPtr<IShape> spOut;
CShape::CreateInstance(&spOut);
if(spOut)
return spOut->QueryInterface(IID_IDispatch, (void**)ppOutObj);
}
return E_INVALIDARG;
}
*/
Canvas.cpp
的
CreateObject
例子使用简单的字符串比较,实际应用中,可以采用hash map等加快对象查找的速度。所以,COM对象有时候也要用其它方法创建为好!
同样的方法,给Layer对象添加属性,以得到 Shapes集合属性:
// MapLib.idl : MapLib
的IDL 源
...
interface
ILayer : IDispatch{
[propget,id(1),helpstring("
得到图形集合对象的引用"
)] HRESULT Shapes([out, retval] IShapes** ppRef);
};
添加实现代码到Layer.h中,你只需注意加粗的文字:
// Layer.h : CLayer
的声明
...
#include"Shapes.h"
// CLayer
class
ATL_NO_VTABLE CLayer :
...
{
public
:
CComPtr<IShapes> m_spShapes;
CLayer()
{
}
...
HRESULT FinalConstruct()
{
return CShapes::CreateInstance(&m_spShapes);
// return S_OK;
}
void FinalRelease()
{
}
public:
STDMETHODIMP get_Shapes(IShapes** ppRef)
{
return m_spShapes.CopyTo(ppRef);
}
};
现在,你的对象模型就建好了。你可以从Canvas根对象得到图层集合(Layers)对象,你还可以向Layers中添加图层。你可以根据索引(目前是以1为基数)得到图层对象(Layer)的引用。从Layer对象得到图形集合对象(Shapes),并进一步操纵Shapes。
五 改变默认的索引基数和修改atlcom.h
最后要把以1为基数的索引,改为以0为基数的索引。这需要修改系统的头文件:atlcom.h。首先找到它,复制一份,更名为atlcom0.h,需要修改的地方我用粗体做了标记:
// atlcom.h--->atlcom0.h
template
<class T,class CollType,class ItemType,class CopyItem,class EnumType>
class
ICollectionOnSTLImpl :public T
{
public
:
STDMETHOD(get_Count)(long* pcount)
{
if (pcount == NULL)
return E_POINTER;
ATLASSUME(m_coll.size()<=LONG_MAX);
*pcount = (long)m_coll.size();
return S_OK;
}
STDMETHOD(get_Item)(long Index, ItemType* pvar)
{
#ifdef ITEM_INDEX_0_BASED
//Index is 0-based
if (pvar == NULL)
return E_POINTER;
if (Index < 0)
return E_INVALIDARG;
HRESULT hr = E_FAIL;
CollType::iterator iter = m_coll.begin();
while (iter != m_coll.end() && Index > 0)
{
iter++;
Index--;
}
if (iter != m_coll.end())
//hr = CopyItem::copy(pvar, &*iter);
hr = CopyItem::copy(pvar, *iter); // CL2
return hr;
#else
//Index is 1-based
if (pvar == NULL)
return E_POINTER;
if (Index < 1)
return E_INVALIDARG;
HRESULT hr = E_FAIL;
Index--;
CollType::const_iterator iter = m_coll.begin();
while (iter != m_coll.end() && Index > 0)
{
iter++;
Index--;
}
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>
STDMETHODIMP IEnumOnSTLImpl<Base, piid, T, Copy, CollType>::Next(ULONG celt, T* rgelt,
ULONG* pceltFetched)
{
if (rgelt == NULL || (celt != 1 && pceltFetched == NULL))
return E_POINTER;
if (pceltFetched != NULL)
*pceltFetched = 0;
if (m_pcollection == NULL)
return E_FAIL;
ULONG nActual = 0;
HRESULT hr = S_OK;
T* pelt = rgelt;
while (SUCCEEDED(hr) && m_iter != m_pcollection->end() && nActual < celt)
{
//hr = Copy::copy(pelt, &*m_iter);
hr = Copy::copy(pelt, *m_iter);// cheungmine
if (FAILED(hr))
{
while (rgelt < pelt)
Copy::destroy(rgelt++);
nActual = 0;
}
else
{
pelt++;
m_iter++;
nActual++;
}
}
if (SUCCEEDED(hr))
{
if (pceltFetched)
*pceltFetched = nActual;
if (nActual < celt)
hr = S_FALSE;
}
return hr;
}
这样,在你所有包含atlcom.h的地方,用atlcom0.h替换,并在包含atlcom0.h前面,加入如下的宏:
// stdafx.h
...
// NOT: #include <atlcom.h>
#define ITEM_INDEX_0_BASED
#include
"atlcom0.h" // 0-based index supports
六 结束语
好了,我要讲的内容都讲完了。你可以编写如下面的js脚本(Canvas.htm)使用这个对象模型:
<
HTML
>
<
HEAD
>
<
script
language="javascript"type="text/javascript">
function Button1_onclick() {
lyrs = Canvas.Layers;
lyr = lyrs.Add();
alert(lyr);
lyr.Shapes.Add();
lyr.Shapes.Add();
lyr.Shapes.Add();
alert("Layers Count:"+lyrs.Count);
alert("Shapes Count:"+lyr.Shapes.Count);
}
function Button2_onclick() {
var cvs =new ActiveXObject("MapLib.Canvas");
alert(cvs);
cvs.Layers.Add();
cvs.Layers.Add();
cvs.Layers.Add();
alert("Layers Count:"+cvs.Layers.Count);
alert ( cvs.Layers.Item(0) ); //
如果显示undefined,说明索引不是以-based
}
</
script
>
</
HEAD
>
<
BODY
>
<
OBJECT
ID="Canvas"CLASSID="CLSID:BC3D7FCC-C1AE-4476-A59C-431457A1173C"></OBJECT>
<inputid="Button1" type="button"value="button"onclick="return Button1_onclick()"/>
<inputid="Button2" type="button"value="button"onclick="return Button2_onclick()"/>
</
BODY
>
</
HTML
>
以上内容,希望对朋友们有所帮助。这些内容看似简单,如果有兴趣,你就完整地做几遍,最后就变成你自己的知识了。我费了一天的时间把它整理出来,希望得到你们的批评指正!点击下面的链接可以得到本文的配套例子代码:
http://download.csdn.net/source/260939
http://blog.csdn.net/cheungmine/article/details/1818913