首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

ATL 實現 COM 對象模型完整指南:從 IDL 設計到集合/枚舉器零基址改造

     編輯:關於VC++

本文系統闡述如何使用 ATL (Active Template Library) 建立類似 DOM/MapObjects 的階層式 COM 對象模型,涵蓋類型庫設計、集合類實現 (ICollectionOnSTLImpl)、枚舉器 (IEnumOnSTLImpl)、0-based 索引改造、以及腳本調用範例。

一、專案初始化與類型庫規劃

  • VS2005 建立 ATL DLL 專案 MapLib,關閉屬性化、字符集設「Not Set」。
  • 核心對象樹:
Canvas (控件/根對象)
 └─ Layers (集合)
      └─ Layer (單元)
           └─ Shapes (集合)
                └─ Shape (單元)

二、IDL 手工調整要點

  1. 介面定義需移入 library 區塊,並前置宣告。
  2. 集合介面 (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); // 自訂擴充
  1. 不可獨立創建的集合類 (Layers, Shapes) 使用 DECLARE_NO_REGISTRY() 並刪除 .rgs 檔。

三、STL 集合實現模板 (stdafx.h 置入)

// 複製策略: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 基於 list,Shapes 基於 vector)

// 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 → Layers, Layer → Shapes)

// 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); }
};

六、關鍵改造:將集合索引從 1-based 改為 0-based

ATL 預設 ICollectionOnSTLImpl::get_Item 為 1-based。需修改系統頭檔:

  1. 複製 atlcom.h → atlcom0.h。
  2. 在 get_Item 中啟用 ITEM_INDEX_0_BASED 區塊,並同步修正 Copy::copy(pvar, *iter) (移除 &)。
  3. 同步修正 IEnumOnSTLImpl::Next 同樣移除 & (見上一篇補遺)。
  4. 專案 stdafx.h 預定義:
#define ITEM_INDEX_0_BASED
#include "atlcom0.h"

七、JavaScript 測試腳本 (驗證 0-based 索引)

<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 快數量級,適合腳本大量創建子對象場景。

完整專案碼:下載連結。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved