Last Modification: January 06, 2004

HOWTO: Implement static object hierarchies in ATL

An object hierarchy consists of a root object that is publicly creatable (e.g. via CoCreateInstance[Ex]), and one or more private objects that can only be accessed through the root object via read-only properties on its interfaces. The latter are usually called nested or child objects. Each nested object can in turn contain other nested objects again accessed through read-only properties on its interface. Typically, nested objects in a hierarchy offer two navigation properties: Parent returns a reference to the immediate parent of this object, and Root returns a reference to the root of the object hierarchy. The actual property names may differ of course.

Although the ability of an object to manufacture other objects through its interfaces is inherent in COM, tightly interconnected hierarchies as we know them appear with the advent of OLE Automation (now only Automation). And for a very good reason - Automation objects can only implement a single dispinterface, thus any structuring can only be performed via nested objects. Object hierarchies can be built with non-Automation objects as well of course. This article won't attempt to go into the architectural details of when to use hierarchies and when to implement multiple interfaces on a single object. Throughout the rest of the article only a single interface is considered per object, though non-Automation hierarchies do not suffer this limitation. The techniques are equally applicable for Automation and non-Automation hierarchies.

There are several approaches to implementing hierarchies. This article concentrates on the simplest case where the complete hierarchy is constructed at the time its root is constructed. The whole hierarchy is alive while the user holds a reference to any of its objects no matter how deep. This approach is applicable for simple hierarchies with small to moderate total amount of data. It is not appropriate for hierarchies holding on to critical resources or just lots of resources in certain key subobjects if these resources are not needed for the rest of the objects in the hierarchy (it's a potential waste of the resources, not to mention they may not be needed at all). In these cases, a dynamic approach is needed whereby the nested objects in a hierarchy are constructed only as needed.

So, with those precautions in mind, here's how to build a static hierarchy. The first step of course is creating the root object. This is typically a simple ATL object (although a control would be appropriate too), whose interface includes one or more [propget] properties for its immediate children objects. In practice, one would use the New ATL Object wizard to create the root object. Note you must NOT use attributed ATL with VC 7.x.

Creating nested objects is probably simpler done without the wizard. If you insist on the wizard, first, ensure you select no aggregation on the properties page and the same threading model as the root object, and second, here's what has to be done to clean up after it has finished:

In the IDL file, add forward declarations for all interfaces after the import block, since there will be lots of cross-referencing. Any nested object may expose the Parent and Root [propget] properties. Any object that has nested objects exposes the nested objects' interfaces via [propget] properties. For example:


[
   object,
   uuid(2ACC43C8-D729-4f2d-95DD-B6B318DEF4F4),
   oleautomation,
   helpstring("IChild Interface"),
   pointer_default(unique)
]
interface IChild : IUnknown
{
   [propget, helpstring("The SubChild")]
   HRESULT SubChild([out, retval] ISubChild **ppChild);

   [propget, helpstring("My Parent")]
   HRESULT Parent([out, retval] IParent **ppParent);

   [propget, helpstring("My Root")]
   HRESULT Root([out, retval] IParent **ppRoot);
};

For any object that has nested objects, its header file should specify the DECLARE_GET_CONTROLLING_UNKNOWN() macro and provide a FinalConstruct() override. This is necessary to automate the reference count forwarding to the root object, and facilitates the implementation of the Root property in nested objects. Then, the object implements the nested object in a member variable using the CComObjectEmbed<> template. This template does not come with ATL unfortunately, use the ComObjectEmbed.h file to get it. It is similar to the template used to implement aggregation in ATL, but it does not forward QueryInterface to the containing object thus maintaining a separate object identity. However, the reference count is handled by the outer object, which in essence means all nested objects will forward their references to the root object and the grand total reference is maintained there. This connection is established in the FinalConstruct of each parent object. Here's a typical implementation of FinalConstruct in an object with a nested object:


// In the header
protected:
   CComObjectEmbed<CSubChild> m_SubChild;

HRESULT CChild::FinalConstruct()
{
   m_SubChild.SetParent(this);
   m_SubChild.SetOuterUnknown(GetControllingUnknown());
   return m_SubChild.FinalConstruct();
}

SetOuterUnknown() ensures the nested object is linked to the passed in IUnknown. GetControllingUnknown() ensures the IUnknown passed will be the one that controls the current object, e.g. the root IUnknown. Since we host the nested object, we have to call its own FinalConstruct - we don't have the equivalent of CComObject::CreateInstance to do this for us. Finally, SetParent is a method that all nested objects should expose so they can be advised who is their immediate parent. This is not necessary for the Root property, because the controlling unknown is always the root of the hierarchy.

Now, let's have a look at how all these properties are implemented. The property exposing a nested object is trivial - we simply call the member object's QueryInterface method:


STDMETHODIMP CChild::get_SubChild(ISubChild **ppSubChild)
{
   return m_SubChild.QueryInterface(ppSubChild);
}

Obtaining the root is equally trivial - we call QueryInterface on the controlling unknown, which, as we already ensured, points to the root object:


STDMETHODIMP CChild::get_Root(IParent **ppRoot)
{
   return GetControllingUnknown()->QueryInterface(IID_IParent, (void**)ppRoot);
}

Obtaining the immediate parent is a bit more contrived. We need to actually store an explicit non-AddRef-ed (raw) pointer to it first, hence the SetParent method I mentioned above:


// In the header
protected:
   IParent *m_pParent;

void CChild::SetParent(IParent *pParent)
{
   m_pParent = pParent;
}

Now it's as simple as returning the interface pointer to the caller, or we can do it in one line by calling QueryInterface:


STDMETHODIMP CChild::get_Parent(IParent **ppParent)
{
   return m_pParent->QueryInterface(IID_IParent, (void**)ppParent);
}

It's only pertinent to note here that if the child object needs services from its parent not exposed though COM interfaces, it would be necessary to store a pointer to the raw C++ class of the parent instead. Similarly, if the nested objects in your hierarchy need services from the root object of the hierarchy no matter the level of nesting, a pointer to the root C++ object should be passed when they are initialized.

The Hier sample is a complete project featuring a hierarchy of three objects: Parent, Child, and SubChild, that illustrates the basic points of creating a simple static hierarchy.

This approach can be extended to static collections as well. Let's say you have an array of nested objects (it can even be a dynamic array that you populate in your FinalConstruct). Just use CComObjectEmbed to wrap the nested objects in the array, and call their SetParent, SetOuterUnknown, and FinalConstruct methods within your FinalConstruct.

One ubiquitous hierarchy most COM programmers are familiar with is the connection points used for firing event notifications. Each connection point is a tiny COM object nested into the event source that holds the client sinks where the events are to be fired. The ATL implementation of connection points uses interface coloring to implement a COM object in a base class - a technique producing similar results to the member object technique detailed in this article - all connection point objects sharing the reference count of the parent source object. Interface coloring is also an applicable technique for building static hierarchies, however, it requires manual and error prone work for coloring the interfaces of the nested objects. Also, it cannot be used for static collections.

References and samples: