Notes on COM aggregation: How do you implement tear-offs in an aggregated object?
Last time, we looked at how an inner or outer object participating in COM aggregation can obtain an interface from its partner without creating a circular reference. We also concluded with a realization that tear-offs create a particular problem with the technique we developed. Let’s fix that.
Again, I’m going to assume that you have a basic understanding of COM tear-offs: A tear-off interface is an interface implemented in an object separate from the main object. The tear-off object maintains a reference-counted pointer to the main object, to which it delegates
QueryInterface calls. When the tear-off object is destroyed, the reference-counted pointer to the main object decrements the reference count on the main object.
The non-aggregating implementation of tear-offs does not delegate
Release. Each tear-off has a private reference count, and reference count operations on the tear-off operate exclusively on that tear-off. However, aggregated objects are required to delegate all the
IUnknown methods to the outer object.
Let’s see what happens if we make tear-offs forward reference counts to the outer object: The outer object queries the inner object for a tear-off. The inner object creates a tear-off, and then calls
AddRef on the outer object. Eventually, the tear-off interface is released by the client. But all reference count operations on the tear-off are forwarded to the outer object. How does the tear-off know when to destroy itself?
Tear-offs of aggregated objects must retain their own reference count so that they know when to destroy themselves. But they also are required to forward reference count operations to the outer object. So which one should it do?
An aggregated tear-off’s
Release method should forward the call to the outer object and also update its own tear-off reference count. When the tear-off reference count reaches zero, the tear-off destroys itself.
This rule also applies to the outer object’s tear-offs, if it has any.
With this rule, we can use our “query, then release” trick for obtaining a pointer to an interface on the aggregated partner:
When querying from outer to inner, the inner object creates a tear-off, with an initial reference count of 1, and which also increments the outer object’s reference count by 1. The outer object performs a
Release on itself to counteract that second
AddRef. To clean up, the outer object performs an
AddRef on itself to undo the artificial
Release, and then releases the tear-off, which both decrements the reference count on the outer object, as well as realizing that it too needs to destroy itself when it decrements its own reference count to zero.
The same calculations apply when querying from the inner object to the outer: The outer object’s tear-off manages its own reference count as well as updating the reference count on the original outer object. This allows the inner object to perform the artificial
Release. And the same sequence of operations applies when the inner object is finished with the tear-off.
Phew, disaster averted.