COM aggregation and ref counting woesPosted on 24 Nov 2006
Why are we talking about Component Object Model (COM), isn’t that old dead technology? Well… no. There are still so many COM objects in use today, in many projects, that you will run into them sooner or later. As a software engineer you might even have to resolve bugs in these components. Today I want to draw attention to ref counting bugs that can creep in when using aggregation within these objects.
COM objects use reference counting to control their lifetime. This is achieved through the implementation of an IUnknown interface by each and every object. This interface contains (quick – what are the first three v-table entries?) IUnknown::QueryInterface, IUnknown::AddRef and IUnknown::Release. So after you add a reference to an object with AddRef, you are expected to call Release when you are finished. Reference counting bugs can crop up in object clients when someone forgets this rule and are usually a real pain to find. This difficulty can be compounded even further if the object itself messes up its implementation of AddRef.
The implementation of AddRef is typically simple (just increment an internal counter), however when the object aggregates other objects in its internal implementation (another form of object reuse as opposed to using containment) it becomes more complex. In these cases you have to ensure that AddRef and Release calls operate on the correct object.
Lets take the example of a hypothetical AIRPLANE object. An AIRPLANE is implemented by aggregating WING, and ENGINE objects. WING implements IWing and IUnknown, ENGINE implements IEngine and IUnknown. Now clients of the AIRPLANE object would expect that IWing::AddRef, and IEngine::AddRef both control the lifetime of the outer object (the component/object doing the reusing – in this case AIRPLANE). This is fair and reasonable, however the only way this can happen is if the inner objects (WING and ENGINE) are aware of the outer object. So when WING and ENGINE are created, AIRPLANE passes a pointer to its IUnknown implementation (called the controlling unknown) down to the inner objects. If they support aggregation then they will use this pointer to handle any IUnknown calls that come in through IWing and IEngine. If they do not support aggregation they will return CLASS_E_NOAGGREGATION and fail creation. However ENGINE and WING must not delegate to the controlling unknown for any AddRef and Release calls that come in through IUnknown itself, as this is what the outer object will use to control their lifetime.
These are just the basic rules of aggregation that when applied ensure that object lifetime is still managed correctly. Common problems arise when the inner objects forget to delegate the AddRef and Release calls to the controlling unknown, or do so in the wrong case (i.e. when called through IUnknown::AddRef). In this case the client of AIRPLANE may see what appears to be a ref counting bug on their side, but is actually an internal issue with the aggregation.
… no wonder people like managed code and .NET. Ref counting bugs can get tricky.