previous page
next page

Object Models

A COM object model is a hierarchy of objects. Collections allow the subobjects to be manipulated. Enumerators allow these objects to be accessed. Most object models have one top-level object and several noncreateable subobjects. The following stylized IDL shows a minimal object model:

library OBJECTMODELLib {
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    // Document sub-object ////////////////////////////////////
    [ object, dual ] interface IDocument : IDispatch {
      [propget] HRESULT Data([out, retval] BSTR *pVal);
      [propput] HRESULT Data([in] BSTR newVal);
    };

    coclass Document {
        [default] interface IDocument;
    };

    // Documents collection ///////////////////////////////////
    [ object, dual ] interface IDocuments : IDispatch {
      HRESULT AddDocument([out, retval] IDocument** ppDocument);
      [propget] HRESULT Count([out, retval] long* pnCount);
      [id(DISPID_VALUE), propget]
      HRESULT Item([in] long n, [out, retval] IDocument** ppdoc);
      [id(DISPID_NEWENUM), propget]
      HRESULT _NewEnum([out, retval] IUnknown** ppEnum);
    };

    coclass Documents {
        [default] interface IDocuments;
    };

    // Application top-level object ///////////////////////////
    [ object, dual ] interface IApplication : IDispatch {
      [propget] HRESULT Documents(
        [out, retval] IDocuments** pVal);
    };

    coclass Application {
        [default] interface IApplication;
    };
};

An instance hierarchy of this object model looks like Figure 8.3.

Figure 8.3. Simple object model instance hierarchy


Implementing the Top-Level Object

The top-level object of an object model is createable and exposes any number of properties and any number of collection subobjects. The example implementation looks like the following:

class ATL_NO_VTABLE CApplication :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CApplication, &CLSID_Application>,
    public IDispatchImpl<IApplication, &IID_IApplication> {
public:
DECLARE_REGISTRY_RESOURCEID(IDR_APPLICATION)
DECLARE_NOT_AGGREGATABLE(CApplication)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CApplication)
    COM_INTERFACE_ENTRY(IApplication)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

  // Create instance of the Documents collection
  HRESULT CApplication::FinalConstruct()
  { return CDocuments::CreateInstance(&m_spDocuments); }

// IApplication
public:
  // Hand out the Documents collection to interested parties
  STDMETHODIMP CApplication::get_Documents(IDocuments** pVal)
  { return m_spDocuments.CopyTo(pVal); }

private:
  CComPtr<IDocuments> m_spDocuments;
};

Implementing the Collection Object

The collection object is the most difficult of the three layers to implement, not because of any difficult code, but because of the maze of type definitions. The first set is required to implement the enumerator:

template <typename T>
struct _CopyVariantFromAdaptItf {
  static HRESULT copy(VARIANT* p1, 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); }
};
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
  _CopyVariantFromAdaptItf<IDocument>,
  list< CAdapt< CComPtr<IDocument> > > >
  CComEnumVariantOnListOfDocuments;

The _CopyVariantFromAdaptItf class is a reusable class that converts an interface into a VARIANT for use in enumerating a collection of interface pointers. The collection object is expected to hold a C++ container of elements of type CAdapt<CComPtr<T>>. Notice how the copy policy is used in the type definition of CComEnumVariantsOnListOfDocuments to obtain the implementation of IEnumVARIANT for the collection object.

The next set of type definitions is for the implementation of the collection methods:

template <typename T>
struct _CopyItfFromAdaptItf {
    static HRESULT copy(T** p1, 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(); }
};

typedef ICollectionOnSTLImpl<
  IDispatchImpl<IDocuments, &IID_IDocuments>,
  list< CAdapt< CComPtr<IDocument> > >,
  IDocument*,
  _CopyItfFromAdaptItf<IDocument>,
  CComEnumVariantOnListOfDocuments>
  IDocumentsCollImpl;

The _CopyItfFromAdaptItf is used to implement the Item property, again assuming a C++ container holding elements of type CAdapt<CComPtr<T>>. The copy policy is then used to define the collection interface implementation, IDocumentsCollImpl.

Finally, IDocumentsCollImpl is used as the base class of the IDocuments implementation:

class ATL_NO_VTABLE CDocuments :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CDocuments>, // noncreateable
    public IDocumentsCollImpl
{
public:
DECLARE_NO_REGISTRY()
DECLARE_NOT_AGGREGATABLE(CDocuments)
DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CDocuments)
    COM_INTERFACE_ENTRY(IDocuments)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// IDocuments
public:
  STDMETHODIMP AddDocument(IDocument** ppDocument) {
    // Create a document to hand back to the client
    HRESULT hr = CDocument::CreateInstance(ppDocument);
    if( SUCCEEDED(hr) ) {
      // Put the document on the list
      CComPtr<IDocument> spDoc = *ppDocument;
      m_coll.push_back(spDoc);
    }

    return hr;
  }
};

The benefit of all the type definitions is that the standard methods of the collection are implemented for us. We only have to implement the AddDocument method, which creates a new CDocument and adds it to the list that the ICollectionOnSTLImpl base class maintains.

Implementing the Subobjects

The subobjects can do whatever you want, including maintaining collections of objects further down the hierarchy. Our example maintains a BSTR, representing its data:

STDMETHODIMP CDocument::get_Data(BSTR *pVal) {
  return m_bstrData.CopyTo(pVal);
}
STDMETHODIMP CDocument::put_Data(BSTR newVal) {
  m_bstrData = newVal;
  return (m_bstrData || !newVal ? S_OK : E_OUTOFMEMORY);
}

Using the Object Model

You normally design an object model to be used by many language mappings, including scripting environments. Here's an example HTML page that uses this example object model:

<html>
<script language=vbscript>
    dim app
    set app = CreateObject("ObjectModel.Application")

    dim docs
    set docs = app.Documents

    dim doc
    set doc = docs.AddDocument
    doc.Data = "Document 1"

    set doc = docs.AddDocument
    doc.Data = "Document 2"

    for each doc in docs
        msgbox doc.data
    next
</script>
</html>


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)