程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 組件對象模型與ATL實現

組件對象模型與ATL實現

編輯:關於VC++

一 概述

微軟組件對象模型(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.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>
<TITLE>[email protected]</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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved