Click here to Skip to main content
15,896,606 members
Articles / Desktop Programming / ATL

Circular Reference-proof ATL Object Collections

Rate me:
Please Sign up or sign in to vote.
3.90/5 (9 votes)
13 Jul 2017CPOL11 min read 68.4K   1.7K   39  
Using ATL and STL to create collections of COM objects without circular references
#pragma once

// These collection helpers provide for a managed collection of objects, each holding a reference
// back to the parent collection, bypassing the problems encountered by circular referencing.

// This is achieved by having the collection contain the data required to create the member objects,
// rather than holding references to the member objects themselves. These MemberData Objects
// DO NOT hold onto a reference to the parent - only the MemberObjects that are generated
// have this reference.

// An example of how this works would be the classic Employee/Department example. 
// Each department is a collection of emplyees, but each employee needs a refrence back to
// their department. Using this method, there would be an EmployeeData object which is collected.
// A possible scenario for a department of 10 employees would be:
// 1.	Client creates a department object. EmployeeData Reference count will be for each 
//		of the 10 objects, and the department reference count is 1 for the client's reference.
// 2.	Client creates a new employee object. This, in turn, creates an employee data object.
//		Employee reference count = 1 for the client reference, and EmployeeData reference count
//		= 1 for reference in employee.
// 3.	New Employee is added to department. The Employee's parent property is set to the
//		department object, increasing its reference count to 2. The EmployeeData is added to
//		the collection, increaing its reference count to 2. The Employee object's reference 
//		count is still only 1, for the client's reference to it.
// 4.	The client retrieves an existing employee from the collection. This creates a new
//		Employee object that is refernced only by the client. This new employee object has
//		a reference to its EmployeeData object, increasing its reference count to 2. The
//		Employee object also has a reference to the department, increasing its reference to 3.
// 5.	The client destroys its own reference to the department. This decreases its reference
//		count to 2, for the references that are still held by the Employee objects.
//		As the department is not yet destroyed, there is no change to any of the EmployeeData
//		reference counts.
// 6.	The client releases it references of the employees. As their reference count was only 1
//		each, these objects get destroyed. This decreases the individual EmployeeData reference
//		counts to 1 (for the reference still held by the department), and then decreases the
//		department's reference count to zero (as there are no more references to it), causing the
//		department to be destroyed. Destroying the department decreases the reference count
//		of the EmployeeData members of the collection to zero, causeing these objects to also
//		be destroyed, and, hey-presto, all references to everything is gone.

/////////////////////////////////////////////////////
// Implementation
// --------------
// There are a few thing required to implement this system of object collections. There are 
// three implementation classes involved in this header, one for the Collection itself, one 
// for the Object exposed by the collection, and one for the data required to generate the
// exposed object. It is this last object that is, in fact, stored in the collection. The
// exposed object references this data object for setting/retrieving its properties.

// CContainer Class
// ----------------
// The first of these classes is the collection object itself. This is implemented by the 
// CContainer class, which is templated to take the typename of the exposed interface,
// and an STL collection type which containes IMemberData interface pointers. Implemetation 
// code is included for list<IMemberData*>, vector<IMemberData*>, set<IMemberData*> and
// map<CComBSTR, IMemberData*> collections. The usage and size of the collection will
// determine your prefernce for STL container.
//
// When you inherit from CContainer, the usual collection interfaces have already been implemented.
// They have, not, however, been exposed by the IContainer interface, allowing the developer
// to choose which of these interfaces should be exposed to the user. These methods include:
//		Add(IMember* pObject)  		Used for adding an object to a list, vector, or set
//									type collection. (Can also be used for a map, but is
//									inefficient as the map key needs to be extracted from
//									the object). The add method extracts the IMemberData
//									object from the IMember interface, and adds that to the
//									collection, incrementing the Data's reference count. This 
//									refernce is destroyed when the collection is destroyed. It
//									also sets the Parent property of the Member object to the
//									collection, incrementing the collection's reference count.
//									This reference is destroyed when the member object is destroyed.
//		Add(VARIANT Index, IMember *Object)
//									This method is similar to the previous Add Method, but
//									associates an index with the member. This is most efficient
//									for a map based collection where the index is a key. The
//									index parameter is ignored for a set based collection, and
//									this method really shouldn't be used in that case. For list
//									and vector based collections, the method assumes that the
//									index is the location that the new object should be inserted 
//									before. If the index is not found, then the object is inserted
//									at the end.
//		Remove(VARIANT Index)		Removes the object identified by Index. If the index is found,
//									the MemberData object is removed from the collection, and its
//									reference count is decremented. Providing there are no
//									other references held to this data (by the client holding a
//									reference to the Member object), then this data object will
//									be destroyed. The only possibility for a logic error here
//									occurs if a member is removed from the collection, and
//									is still referenced by the client, the client's reference
//									will still point to the collection in it's parent property.
//		get_Item(VARIANT Index, IMember **ppObject)
//									This method actually creates a new Member object from the
//									data stored in the collection. The member includes a reference 
//									to the parent, and to the data object, but only the client 
//									holds a reference to this newly created object.
//		Count(long *pCount)			returns the number of objects in the collection
//		get__NewEnum(IUnknown **ppUnk)
//									Returns an enumerator object (implementing IEnumVARIANT).
//									The enumerator contains a reference to the collection, so 
//									the collection will not get destroyed while there are 
//									enumerators active.

// CMember class
// -------------
// Inherit your exposed collection members from the CMember class. This class is templated
// such that you pass your Member Data class, inherited from IMemberData, and the interface
// implemented by your MemberData class. This can be IMemberData, or any other interface
// implemented by the class. It is good idea is to implement the same interface as the CMember
// exposes, to allow simple transfreeing of data from the data object to the exposed
// object.
//
// Not that the CMember constructor creates a new CMemebrData object, and holds on to its COM 
// reference in the m_pData member. This allows a createable object that can be added to
// a collection. If the object is being created from the Data Object (ie: it is being retrieved 
// from the collection), then this data object is replaced by the one in storeed in the collection.

// CMember has the following members.
//		CComPtr<IMemberDataInterface> m_pData
//									This member holds the reference to the data object.
//		CComPtr<IContainer> m_pParent
//									A reference to the parent container object
//		get_Data(IMemberData **ppData)
//									Returns the reference to the data object through COM
//		put_Data(IMemberData *pData)
//									Sets the COM reference to the data object
//		get_Parent(IContainer *pParent)
//									Returns the COM reference to the parent collection.

// CMemberData Class
// -----------------
// The CMemberData is the class that holds the identifying data to create the exposed 
// objects of your collection on demand. References to these objects are what is actually
// stored in the collection, and are used to create the objects your collection actually 
// wants to expose.
// The simplest idea is to have your exppoed object's full implementation taken care of
// in this Data Object, with the exception of methods that deal directly with the parent.
// YOU CANNOT HOLD ANY REFERENCES TO THE PARENT COLLECTION, OR TO THE EXPOSED OBJECTS IN
// THIS OBJECT, OR THE WHOLE SYSTEM FALLS DOWN. Your exposed object simply passes its 
// exposed interface methods through to this object to produce the desired results.
// This class takes a single template parameter, which is your exposed Coclass name, to
// enable this object to create instances of the exposed object. The CreateObject method
// is implemented to perform this function.
// CMemberData also declares a number of other methods, which are designed to help with
// the indexing and referenceing of the objects within the STL collection. The CanCompareXXX
// and CompareXXX methods no not need to be implemented unless you want to index
// your collections on some property of the data. See the description of the these methods
// below for further details.
//
//		CreateObject(IMember **ppObject);
//									This method uses your CoClass to create an instance
//									if your exposed class, and then sets the reference to
//									its data to "this".
//		CanCompareKeys();
//									If the CompareKey() and the get_Key methods have been
//									implemented for this object, then this method should
//									return S_OK. Otherwise, this method should return S_FALSE.
//		CanCompareObjects();
//									This method returns S_OK if the CompareObject method has 
//									been implemented, or S_FALSE if it hasn't.
//		get_Key(BSTR *pKey);
//									By default, this method returns E_NOTIMPL. However, if
//									your objects contain some sort of Key field, you can
//									implement this method to allow the creation of map
//									type collections, where the index of the map is included
//									in the object.
//		CompareKey(BSTR sKey, short *pCompare);
//									This method compares a given string key with the object
//									itself, and returns a -ve, 0, or +ve result in pCompare
//									if the objects key is less than, equal to, or greater than
//									the supplied key respectively.
//		CompareObject(IMemberData *pObject, short *pCompare);
//									By default, this method returns E_NOTIMPL. It is included
//									so that the developer may implement a comparison function
//									on the member objects themselves. If this function is
//									implemented, CanCompareObjects should be overwritten
//									to return S_OK. The function should return a -ve, 0, or
//									+ve value in pCompare if the object being compared is
//									less than, equal to, or greater than the current object
//									respectively.


// STL Collection helpers
// ----------------------
// A less than and an equal to operator for IMemberData interface pointers
// These work whether or not the CompareXXX interfaces have been implemented


#include <list>
#include <vector>
#include <set>
#include <map>
#include <functional>

template <>
struct std::less<IMemberData*> {
	bool operator()(const IMemberData* p1, const IMemberData* p2) const {
		IMemberData *m1 = (IMemberData*) p1;
		IMemberData *m2 = (IMemberData*) p2;

		short nCompare = 0;
		if (m1->CanCompareObjects() == S_OK)
			m1->CompareObject(m2, &nCompare);
		else if (m1->CanCompareKeys() == S_OK) {
			CComBSTR sKey;
			m2->get_Key(&sKey);
			m1->CompareKey(sKey, &nCompare);
		}
		else if (p1 < p2)
			nCompare = -1;
		return nCompare < 0;
	}
};

template <>
struct std::equal_to<IMemberData*> {
	bool operator()(const IMemberData* p1, const IMemberData* p2) const {
		IMemberData *m1 = (IMemberData*) p1;
		IMemberData *m2 = (IMemberData*) p2;

		short nCompare = -1;
		if (m1->CanCompareObjects() == S_OK)
			m1->CompareObject(m2, &nCompare);
		else if (m1->CanCompareKeys() == S_OK) {
			CComBSTR sKey;
			m2->get_Key(&sKey);
			m1->CompareKey(sKey, &nCompare);
		}
		else if (p1 == p2)
			nCompare = 0;
		return nCompare == 0;
	}
};

// The following class implements a find() method for the STL collection types
//		std::vector<IMemberData*>
//		std::list<IMemberData*>
//		std::set<IMemberData*>
//		std::map<CComBSTR, IMemberData*>

template <typename STLContainer>
class CSTLContainer : public STLContainer {
public:
	typedef STLContainer CollType;
	typedef typename STLContainer::iterator iterator;
public:
	CSTLContainer() { }
	virtual ~CSTLContainer() {
		for (iterator it = begin(); it != end(); ++it)
			Reference(it)->Release();
	}
	IMemberData* Reference(iterator it) { return *it; }
	iterator find(VARIANT Index);
	bool insert(VARIANT Index, IMemberData *pData);
	bool append(IMemberData *pData);
	bool erase(VARIANT Index) {
		// This one will work the same for all collection types
		iterator it = find(Index);
		if (it != end()) {
			Reference(it)->Release();
			CollType::erase(it);
			return true;
		}
		return false;
	}
};

// implementations for vector<IMemberObject*> collections
template < >
inline std::vector<IMemberData*>::iterator CSTLContainer<std::vector<IMemberData*> >::find(VARIANT Index) {
	iterator it;
	CComVariant vIndex;
	if (size() == 0)
		return end();
	if (Index.vt == VT_DISPATCH || Index.vt == VT_UNKNOWN) {
		CComQIPtr<IMemberData> pMember;
		if (Index.vt == VT_DISPATCH)
			pMember = Index.pdispVal;
		else
			pMember = Index.punkVal;
		if (pMember->CanCompareKeys() == S_FALSE) {
			std::equal_to<IMemberData*> MemberEquals;
			for (it = begin(); it != end(); ++it) {
				if (MemberEquals(pMember, *it))
					break;
			}
		}
		else {
			// Do this through a key comparison - it is a bit more efficient as we only need
			// to get pMember's key once.
			CComBSTR sKey;
			pMember->get_Key(&sKey);
			for (it = begin(); it != end(); ++it) {
				short nCompare;
				(*it)->CompareKey(sKey, &nCompare);
				if (nCompare == 0)
					break;
			}
		}
	}
	else if (Index.vt == VT_BSTR || Index.vt == (VT_BSTR | VT_BYREF) && (*begin())->CanCompareKeys() == S_OK) {
		vIndex.ChangeType(VT_BSTR, &Index);
		for (it = begin(); it != end(); ++it) {
			short nCompare;
			(*it)->CompareKey(vIndex.bstrVal, &nCompare);
			if (nCompare == 0)
				break;
		}
	}
	else if (SUCCEEDED(vIndex.ChangeType(VT_I4, &Index))) {
		if (vIndex.lVal > 0 && vIndex.lVal <= (long)size()) {
			it = begin() + (vIndex.lVal - 1);
		}
		else // Invalid index
			it = end();
	}
	else	// Invalid index
		it = end();
	return it;
}

template < >
inline bool CSTLContainer<std::vector<IMemberData*> >::insert(VARIANT IndexBefore, IMemberData* pMember) {
	bool bSuccess = false;
	try {
		iterator it = CollType::insert(find(IndexBefore), pMember);
		if (it != end()) {
			pMember->AddRef();
			bSuccess = true;
		}
		else
			bSuccess = append(pMember);
	}
	catch(...) { }
	return bSuccess;
}

template <>
inline bool CSTLContainer<std::vector<IMemberData*> >::append(IMemberData *pMember) {
	bool bSuccess = false;
	try {
		CollType::push_back(pMember);
		pMember->AddRef();
		bSuccess = true;
	}
	catch(...) { }
	return bSuccess;
}

// list<IMemberData*> implementations
template < >
inline std::list<IMemberData*>::iterator CSTLContainer<std::list<IMemberData*> >::find(VARIANT Index) {
	iterator it;
	CComVariant vIndex;
	if (size() == 0)
		return end();
	if (Index.vt == VT_DISPATCH || Index.vt == VT_UNKNOWN) {
		CComQIPtr<IMemberData> pMember;
		if (Index.vt == VT_DISPATCH)
			pMember = Index.pdispVal;
		else
			pMember = Index.punkVal;
		if (pMember->CanCompareKeys() == S_FALSE) {
			std::equal_to<IMemberData*> MemberEquals;
			for (it = begin(); it != end(); ++it) {
				if (MemberEquals(pMember, *it))
					break;
			}
		}
		else {
			// Do this through a key comparison - it is a bit more efficient as we only need
			// to get pMember's key once.
			CComBSTR sKey;
			pMember->get_Key(&sKey);
			for (it = begin(); it != end(); ++it) {
				short nCompare;
				(*it)->CompareKey(sKey, &nCompare);
				if (nCompare == 0)
					break;
			}
		}
	}
	else if (Index.vt == VT_BSTR || Index.vt == (VT_BSTR | VT_BYREF) && (*begin())->CanCompareKeys() == S_OK) {
		vIndex.ChangeType(VT_BSTR, &Index);
		for (it = begin(); it != end(); ++it) {
			short nCompare;
			(*it)->CompareKey(vIndex.bstrVal, &nCompare);
			if (nCompare == 0)
				break;
		}
	}
	else if (SUCCEEDED(vIndex.ChangeType(VT_I4, &Index))) {
		it = begin();
		while (it != end() && --vIndex.lVal > 0)
			++it;
	}
	else	// Invalid index
		it = end();
	return it;
}

template < >
inline bool CSTLContainer<std::list<IMemberData*> >::insert(VARIANT IndexBefore, IMemberData* pMember) {
	bool bSuccess = false;
	try {
		iterator it = CollType::insert(find(IndexBefore), pMember);
		if (it != end()) {
			pMember->AddRef();
			bSuccess = true;
		}
		else
			bSuccess = append(pMember);
	}
	catch(...) { }
	return bSuccess;
}

template < >
inline bool CSTLContainer<std::list<IMemberData*> >::append(IMemberData *pMember) {
	bool bSuccess = false;
	try {
		CollType::push_back(pMember);
		pMember->AddRef();
		bSuccess = true;
	}
	catch(...) { }
	return bSuccess;
}

// set<IMemberData*> implementations.
// NOTE: Any collection saving its data in a set should implement the IMemberData::CompareObjects
// interface on the member objects. Otherwise, the <set> just compares interface pointer values.

template < >
inline std::set<IMemberData*>::iterator CSTLContainer<std::set<IMemberData*> >::find(VARIANT Index) {
	iterator it;
	CComVariant vIndex;
	if (size() == 0)
		return end();
	if (Index.vt == VT_DISPATCH || Index.vt == VT_UNKNOWN) {
		CComQIPtr<IMemberData> pMember;
		if (Index.vt == VT_DISPATCH)
			pMember = Index.pdispVal;
		else
			pMember = Index.punkVal;
		return CollType::find(pMember);
	}
	else if (Index.vt == VT_BSTR || Index.vt == (VT_BSTR | VT_BYREF) && (*begin())->CanCompareKeys() == S_OK) {
		vIndex.ChangeType(VT_BSTR, &Index);
		for (it = begin(); it != end(); ++it) {
			short nCompare;
			(*it)->CompareKey(vIndex.bstrVal, &nCompare);
			if (nCompare == 0)
				break;
		}
	}
	else if (SUCCEEDED(vIndex.ChangeType(VT_I4, &Index))) {
		it = begin();
		while (it != end() && --vIndex.lVal > 0)
			++it;
	}
	else	// Invalid index
		it = end();
	return it;
}

// The insert function ignores the key value, as sets are stored in key order regardless
template < >
inline bool CSTLContainer<std::set<IMemberData*> >::insert(VARIANT, IMemberData* pMember) {
	return append(pMember);
}

template < >
inline bool CSTLContainer<std::set<IMemberData*> >::append(IMemberData *pMember) {
	bool bSuccess = false;
	try {
		if (CollType::insert(pMember).second) {
			pMember->AddRef();
			bSuccess = true;
		}
	}
	catch (...) { }
	return bSuccess;
}

// map<CComBSTR, IMemberData*> Implementation

template < >
inline IMemberData* CSTLContainer<std::map<CComBSTR, IMemberData*> >::Reference(std::map<CComBSTR, IMemberData*>::iterator it) {
	return it->second;
}

template < >
inline std::map<CComBSTR, IMemberData*>::iterator CSTLContainer<std::map<CComBSTR, IMemberData*> >::find(VARIANT Index) {
	iterator it;
	CComVariant vIndex;
	if (size() == 0)
		return end();
	if (Index.vt == VT_DISPATCH || Index.vt == VT_UNKNOWN) {
		CComQIPtr<IMemberData> pMember;

		if (Index.vt == VT_DISPATCH)
			pMember = Index.pdispVal;
		else
			pMember = Index.punkVal;
		if (pMember->CanCompareKeys() == S_OK) {
			CComBSTR sKey;
			pMember->get_Key(&sKey);
			return CollType::find(sKey);
		}
	}
	if (Index.vt != VT_BSTR && Index.vt != (VT_BSTR | VT_BYREF) && SUCCEEDED(vIndex.ChangeType(VT_I4, &Index))) {
		it = begin();
		while (it != end() && --vIndex.lVal > 0)
			++it;
	}
	else if (SUCCEEDED(vIndex.ChangeType(VT_BSTR, &Index))) {
		CComBSTR sKey = vIndex.bstrVal;
		return CollType::find(sKey);
	}
	else	// Invalid index
		it = end();
	return it;
}

// The insert function assumes that the IndexBefore parameter is the new key value.
template < >
inline bool CSTLContainer<std::map<CComBSTR, IMemberData*> >::insert(VARIANT IndexBefore, IMemberData *pMember) {
	bool bSuccess = false;
	CComVariant vIndex;
	try {
		if (SUCCEEDED(vIndex.ChangeType(VT_BSTR, &IndexBefore)))
			if (CollType::insert(CollType::value_type(vIndex.bstrVal, pMember)).second) {
				pMember->AddRef();
				bSuccess = true;
			}
	}
	catch(...) { }
	return bSuccess;
}

// The append method only works if the get_Key method if the IMemberData object is implemented
template < >
inline bool CSTLContainer<std::map<CComBSTR, IMemberData*> >::append(IMemberData *pMember) {
	CComBSTR sKey;
	bool bSuccess = false;
	try {
		if (SUCCEEDED(pMember->get_Key(&sKey)))
			if (CollType::insert(CollType::value_type(sKey, pMember)).second) {
				pMember->AddRef();
				bSuccess = true;
			}
	}
	catch (...) { }
	return bSuccess;
}

// This is the actual collection object implementation.
// It will work for collections of any objects derrived from IMemberData.
// The collection must object itself should inherit from this class, which implements the 
// IContainer interface. Note that there are also implementations for the standard collection interfaces,
// but they have not been included in the IContainer interface, so that the
// user can expose only those methods they wish to. 

template <typename IExposed, typename STLContainerType = std::vector<IMemberData*> >
// IExposed is the interface of the exposed objects contained by the collection
//			Your Exposed class should be derrived from CMember, or at least implement the IMember interface
// The STLContainer type defines the container you use store the data objects.
//		The default is a vector, which allows for fast random access based on an offset
//		Implementations have been allowed for list, set, and map (indexed on BSTR) also.
//		The container contents should all implement the IMemberData interface, which knows
//		how to create an object that implements IExposed.
class CContainer : public IContainer {
public:
	// useful typedefs
	typedef IExposed Exposed;
	typedef CSTLContainer<STLContainerType> CollType;
	typedef typename STLContainerType::iterator iterator;
	typedef CContainer<IExposed, STLContainerType> Container;
public:
	// Construction and Destruction
	CContainer() { }
public:
	// The collection itself
	CollType m_coll;
public:
	// An Enumerator class for the collection
	class CEnumerator : public IEnumVARIANT {
	private:
		ULONG m_uRef;
	public:
		CEnumerator(CContainer *pContainer) : m_pParent(pContainer), m_uRef(0) { m_pParent->AddRef(); m_it = m_pParent->m_coll.begin(); }
		~CEnumerator() { m_pParent->Release(); }
		CContainer* m_pParent;
		iterator m_it;
	public:
		// IUnknown Implementation
		virtual ULONG STDMETHODCALLTYPE AddRef() { return ++m_uRef; }
		virtual ULONG STDMETHODCALLTYPE Release() {
			if (--m_uRef)
				return m_uRef;
			delete this;
			return 0;
		}
		STDMETHOD (QueryInterface)(REFIID iid, void **ppVal) {
			if (IsEqualIID(iid, IID_IUnknown))
				*ppVal = (IUnknown*)this;
			else if (IsEqualIID(iid, IID_IEnumVARIANT))
				*ppVal = (IEnumVARIANT*)this;
			else {
				*ppVal = NULL;
				return E_NOINTERFACE;
			}
			AddRef();
			return S_OK;
		}
		// IEnumVARIANT Implementation
		STDMETHOD (Clone)(IEnumVARIANT FAR* FAR* ppEnum) {
			CEnumerator *pEnum = new CEnumerator(m_pParent);
			if (pEnum) {
				pEnum->AddRef();
				pEnum->m_it = m_it;
				*ppEnum = (IEnumVARIANT*)pEnum;
				return S_OK;
			}
			return E_OUTOFMEMORY;
		}
		STDMETHOD (Next)(ULONG celt, VARIANT FAR* rgVar, ULONG FAR* pFetched) {
			ULONG nFetch = 0;
			while(nFetch < celt && m_it != m_pParent->m_coll.end()) {
				CComPtr<IMemberData> pData = m_pParent->m_coll.Reference(m_it);
				CComPtr<IMember> pMember;
				HRESULT hr = pData->CreateObject(&pMember);
				if (FAILED(hr))
					return hr;
				CComPtr<IContainer> pContainer;
				m_pParent->QueryInterface(&pContainer);
				pMember->put_Parent(pContainer);
				CComQIPtr<IDispatch> pDisp = pMember;
				rgVar->vt = VT_DISPATCH;
				rgVar->pdispVal = pDisp.Detach();
				rgVar++;
				m_it++;
				nFetch++;
			}
			if (pFetched)
				*pFetched = nFetch;
			return nFetch == celt ? S_OK : S_FALSE;
		}
		STDMETHOD (Reset)() { m_it = m_pParent->m_coll.begin(); return S_OK; }
		STDMETHOD (Skip)(ULONG celt) {
			while (celt && m_it != m_pParent->m_coll.end()) {
				celt--;
				m_it++;
			}
			return celt ? S_FALSE : S_OK;
		}
	};
public:
	// helpers
	iterator find(VARIANT Index) { return m_coll.find(Index); }
	long size() { return (long)m_coll.size(); }
	iterator begin() { return m_coll.begin(); }
	iterator end() { return m_coll.end(); }
public:
	STDMETHOD (Add)(IExposed* pObject) {
		CComQIPtr<IMember> pMember = pObject;	// Your member objects sjhould also implement IMember interface
		if (!pMember)
			return E_INVALIDARG;
		CComPtr<IMemberData> pData;
		HRESULT hr = pMember->get_Data(&pData);
		if (FAILED(hr))
			return hr;
		if (!pData)		// Data object not initialised in the member object
			return E_FAIL;
		if (m_coll.append(pData)) {
			CComPtr<IContainer> pContainer;
			QueryInterface(&pContainer);
			pMember->put_Parent(pContainer);
			return S_OK;
		}
		return E_FAIL;
	}

	STDMETHOD (Add)(VARIANT Index, IExposed *pObject) {
		CComQIPtr<IMember> pMember = pObject;	// Your member objects should also implement IMember interface
		if (!pMember)
			return E_INVALIDARG;
		CComPtr<IMemberData> pData;
		HRESULT hr = pMember->get_Data(&pData);
		if (FAILED(hr))
			return hr;
		if (!pData)		// Data object not initialised in the member object
			return E_FAIL;
		if (m_coll.insert(Index, pData)) {
			CComPtr<IContainer> pContainer;
			QueryInterface(&pContainer);
			pMember->put_Parent(pContainer);
			return S_OK;
		}
		return E_FAIL;
	}

	STDMETHOD (Remove)(VARIANT Index) {
		return m_coll.erase(Index) ? S_OK : E_INVALIDARG;
	}

	STDMETHOD (get_Item)(VARIANT Index, IMember **pMember) {
		iterator it = m_coll.find(Index);
		if (it == m_coll.end())
			return E_INVALIDARG;
		CComQIPtr<IMemberData> pData = m_coll.Reference(it);
		if (!pData)	// Collection should only contain pointers IMemberData objects.
			return E_UNEXPECTED;
		HRESULT hr = pData->CreateObject(pMember);
		if (SUCCEEDED(hr) && pMember) {
			CComPtr<IContainer> pContainer;
			QueryInterface(&pContainer);
			hr = (*pMember)->put_Parent(pContainer);
		}
		return hr;
	}
	STDMETHOD (get_Count)(long *pCount) { 
		*pCount = (long)m_coll.size(); 
		return S_OK; 
	}
	STDMETHOD (get__NewEnum)(IUnknown **ppUnk) {
		CEnumerator *pEnum = new CEnumerator(this);
		if (pEnum) {
			pEnum->AddRef();
			*ppUnk = (IUnknown*)pEnum;
			return S_OK;
		}
		return E_OUTOFMEMORY;
	}
};

// This class implements the required methods for your MemberData objects
// Implementation of the CompareObject, CompareKey, and get_Key methods is optional
// but should be included in the following cases:
//	1.	If your collection is based on a set, the the ComapreObject method should be
//		implemented, and the CanCompareObject method should be overwritten to return true
//  2.	If your collection is based on a map with a BSTR index, then you can implement the 
//		get_Key and CompareKey methods to allow automatic retrieval of the index value.
//	3.  You need to implement the get_Key and the CompareKey methods if you want the user 
//		access your collection by a string, unless your collection is based on map<CComBSTR, IMemberData*>
//
// The CanCompareObjects and CanCompareKeys methods should be overwritten to return S_OK
// if the CompareObjects and CompareKeys methods are implemented. By default, these methods return S_FALSE
//
// This class implements a default method for CreateObject, which returns an interface pointer
// to an IMember object. Your derrived class may wish to implement an alternative CreateObject,
// which returns an interface to your class derived from IMember. The easy way to do this
// would be something like :
// 
// STDMETHODIMP CMyMemberData::CreateObject(IMyMemberInterface **ppMyMember) {
//		CComPtr<IMember> pMember;
//		HRESULT hr = CMemberData::CreateObject(&pMember)
//		if (SUCCEEDED(hr))
//			hr = pMember->QueryInterface(ppMyMember);
//		return hr;
// }

template <typename CMemberClass>
// The template parameter here is your Member class, which implements IMember, and is inherited from CComObjectRoot.
// This implementation uses CComObject<CMemberClass>::CreateInstance in order to create the instance
// of the Member object. Note at this time, it has not yet been added to a collection,
// so the parent member of your member will still be NULL
class CMemberData: public IMemberData {
public:
	typedef CMemberClass MemberCoclass;
	STDMETHOD (CreateObject)(IMember **ppObject) {
		CComObject<CMemberClass> *pNewObject = NULL;
		HRESULT hr = CComObject<CMemberClass>::CreateInstance(&pNewObject);
		if (SUCCEEDED(hr)) {
			if (FAILED(hr = pNewObject->QueryInterface(IID_IMember, (void**)ppObject)))
				delete pNewObject;
		}
		pNewObject->put_Data((IMemberData*)this);
		return hr;
	}
	// These following methods are implemented in this class, simply returning a not implemented
	// HRESULT. Implementing these is not to difficult, however. If you return S_OK from 
	STDMETHOD (CanCompareObjects)() { return S_FALSE; }
	STDMETHOD (CanCompareKeys)() { return S_FALSE; }
	STDMETHOD (get_Key)(BSTR *pKey) { return E_NOTIMPL; }
	STDMETHOD (CompareKey)(BSTR sKey, short *pCompare) {return E_NOTIMPL; }
	STDMETHOD (CompareObject)(IMemberData *pData, short *pCompare) { return E_NOTIMPL; }
};


// This last class is what you inherit them member objects from. It implements the IMember interface, to
// control bot the data and parent (collection) reference counting.

template <class CMemberDataClass, typename IMemberDataInterface>
// The 1st template object is the your class inherited from CMemberData. This allows the inital construction
// to create an empty object on creation (so that objects can be created outside of the collection,
// and later added to the collection. The second template member is the interface implemented
// by the data object, which often would be the same as the interface exposed by the Member
// object. This allows simple transfer of data from the exposed object to the data object.
// eg: MyMember::get_Property(*pValue) { return m_pData->get_Property(pValue); }

class CMember : public IMember {
public:
	// The References to the data object and the Collection (parent) object
	CComPtr<IMemberDataInterface> m_pData;
	CComPtr<IContainer> m_pParent;
public:
	// The constructor creates a new, empty data object for use when this member is
	// object is being created from new. If the object is being created FROM a data object,
	// then this new data object will be replaced.
	CMember() {
		CComObject<CMemberDataClass> *pData = NULL;
		if (SUCCEEDED(CComObject<CMemberDataClass>::CreateInstance(&pData))) {
			pData->QueryInterface(&m_pData);
		}
	}
	// Attaches the data to object to the member
	STDMETHOD (put_Data)(IMemberData *pData) {
		if (m_pData)
			m_pData.Release();
		if (pData)
			return pData->QueryInterface(&m_pData);
		// Set to NULL
		return S_OK;
	}
	STDMETHOD (get_Data)(IMemberData **ppData) {
		if (m_pData)
			return m_pData.QueryInterface(ppData);
		return E_NOINTERFACE;
	}
	STDMETHOD (put_Parent)(IContainer *pContainer) {
		// There can be only one
		if (m_pParent)
			m_pParent.Release();
		m_pParent = pContainer;
		return S_OK;
	}
	STDMETHOD (get_Parent)(IContainer **ppContainer) {
		if ((*ppContainer = m_pParent.p) != NULL)
			(*ppContainer)->AddRef();
		return S_OK;
	}
};

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Australia Australia
Been programming for 40 years now, starting when I was 13 on DEC PDP 11 (back in the day of paper tape storage, and hex switch boot procedures). Got right into micro-computers from an early age, with machines like the Dick Smith Sorcerer and the CompuColor II. Started CP/M and MS-DOS programming in the mid 1980's. By the end of the '80's, I was just starting to get a good grip on OOP (Had Zortech C++ V1.0).

Got into ATL and COM programming early 2002. As a result, my gutter vocabulary has expanded, but it certainly keeps me off the streets.

Recently, I have had to stop working full time as a programmer due to permanent brain damage as a result of a tumour (I just can't keep up the pace required to meet KPI's). I still like to keep my hand in it, though, and will probably post more articles here as I discover various tricky things.

Comments and Discussions