组件对象模型与ATL实现

来源:互联网 发布:js 取消input选中 编辑:程序博客网 时间:2024/05/01 05:25
组件对象模型与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;
 }
 
 static void init(VARIANT* p) { VariantInit(p); }
 static void 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;
 }
 
 static void init(T** p) {}
 static void 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>
using namespace 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.cppCreateObject例子使用简单的字符串比较,实际应用中,可以采用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>
<TITLE>cheungmine@gmail.com</TITLE>
<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>
    <input id="Button1" type="button" value="button" onclick="return Button1_onclick()" />
    <input id="Button2" type="button" value="button" onclick="return Button2_onclick()" />
</BODY>
</HTML>
 
以上内容,希望对朋友们有所帮助。这些内容看似简单,如果有兴趣,你就完整地做几遍,最后就变成你自己的知识了。我费了一天的时间把它整理出来,希望得到你们的批评指正!点击下面的链接可以得到本文的配套例子代码:
 http://download.csdn.net/source/260939
 
张亮
2007-10-10初稿
2007-10-11修改
上海浦东
转载请注明出处!