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.
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>
|