一 概述
微軟組件對象模型(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