本文系統闡述如何使用 ATL (Active Template Library) 建立類似 DOM/MapObjects 的階層式 COM 對象模型,涵蓋類型庫設計、集合類實現 (ICollectionOnSTLImpl)、枚舉器 (IEnumOnSTLImpl)、0-based 索引改造、以及腳本調用範例。
MapLib,關閉屬性化、字符集設「Not Set」。Canvas (控件/根對象)
└─ Layers (集合)
└─ Layer (單元)
└─ Shapes (集合)
└─ Shape (單元)
library 區塊,並前置宣告。ILayers, IShapes) 必須包含標準三成員:[propget] HRESULT Count([out,retval] long* nItems);
[propget, id(DISPID_VALUE)] HRESULT Item([in] long index, [out,retval] ILayer** ppRef);
[propget, id(DISPID_NEWENUM), hidden] HRESULT _NewEnum([out,retval] IUnknown** ppEnum);
[id(1)] HRESULT Add([out,retval] ILayer** ppRef); // 自訂擴充
Layers, Shapes) 使用 DECLARE_NO_REGISTRY() 並刪除 .rgs 檔。// 複製策略:STL 容器元素 (CAdapt>) → VARIANT (給 _NewEnum)
template <typename T>
struct _CopyVariantFromAdaptItf {
static HRESULT copy(VARIANT* p1, const CAdapt>& p2) {
HRESULT hr = p2.m_T->QueryInterface(IID_IDispatch, (void**)&p1->pdispVal);
if (SUCCEEDED(hr)) { p1->vt = VT_DISPATCH; return hr; }
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); }
};
// 複製策略:STL 容器元素 → 介面指標 (給 Item)
template <typename T>
struct _CopyItfFromAdaptItf {
static HRESULT copy(T** p1, const CAdapt>& 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.h
#include "Layer.h"
typedef CComEnumOnSTL< IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
_CopyVariantFromAdaptItf, list>> >
CComEnumVariantOnListOfLayers;
typedef ICollectionOnSTLImpl< IDispatchImpl,
list>>, ILayer*,
_CopyItfFromAdaptItf, CComEnumVariantOnListOfLayers >
ILayerCollImpl;
class ATL_NO_VTABLE CLayers : public CComObjectRootEx,
public CComCoClass, public ISupportErrorInfo, public ILayerCollImpl
{
DECLARE_NO_REGISTRY()
BEGIN_COM_MAP(CLayers)
COM_INTERFACE_ENTRY(ILayers) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
// Add 方法實現
STDMETHODIMP Add(ILayer** ppRef) {
CComPtr sp; HRESULT hr = CLayer::CreateInstance(&sp);
if (SUCCEEDED(hr)) { m_coll.push_back(sp); return sp.CopyTo(ppRef); }
return hr;
}
};
OBJECT_ENTRY_AUTO(__uuidof(Layers), CLayers)
Shapes.h 同理,將 list 換為 vector,介面換為 IShape。
// Canvas.h
#include "Layers.h"
class CCanvas : public ... {
CComPtr m_spLayers;
HRESULT FinalConstruct() { return CLayers::CreateInstance(&m_spLayers); }
STDMETHODIMP get_Layers(ILayers** ppRef) { return m_spLayers.CopyTo(ppRef); }
};
// Layer.h
#include "Shapes.h"
class CLayer : public ... {
CComPtr m_spShapes;
HRESULT FinalConstruct() { return CShapes::CreateInstance(&m_spShapes); }
STDMETHODIMP get_Shapes(IShapes** ppRef) { return m_spShapes.CopyTo(ppRef); }
};
ATL 預設 ICollectionOnSTLImpl::get_Item 為 1-based。需修改系統頭檔:
atlcom.h → atlcom0.h。get_Item 中啟用 ITEM_INDEX_0_BASED 區塊,並同步修正 Copy::copy(pvar, *iter) (移除 &)。IEnumOnSTLImpl::Next 同樣移除 & (見上一篇補遺)。stdafx.h 預定義:#define ITEM_INDEX_0_BASED
#include "atlcom0.h"
<OBJECT ID="Canvas" CLASSID="CLSID:BC3D7FCC-C1AE-4476-A59C-431457A1173C"></OBJECT>
<script>
function test() {
var lyrs = Canvas.Layers;
var lyr = lyrs.Add(); // 索引 0
lyr.Shapes.Add(); lyr.Shapes.Add(); lyr.Shapes.Add();
alert("Layers: " + lyrs.Count); // 1
alert("Shapes: " + lyr.Shapes.Count); // 3
alert("Item(0): " + lyrs.Item(0)); // 0-based 正常返回物件
alert("Item(1): " + lyrs.Item(1)); // 越界返回 undefined
}
</script>
在 ICanvas 增加 CreateObject(bstrName, ppOutObj),內部 new CComObject 直接 C++ 實例化,比 CoCreateInstance 快數量級,適合腳本大量創建子對象場景。
完整專案碼:下載連結。