COM aggregation, "stabilization" AddRef/Release pair, necessary? (Don Box book)

masterjodi 40 Reputation points
2024-04-29T07:02:22.5866667+00:00

To the excellent members of the COM team:

 

I have one question with the “stabilization” technique introduced in Don Box’s book.

 

In functions like FinalConstruct(), the outer object might create inner aggregation objects. These inner objects in turn might need to QueryInterface() outer object for communication. On page 96 of Don’s book, the inner object queries the “truck” interface of the outer in the inner’s Initialize().

 

It is mentioned on page 96 that the Initialize() code might accidently call release on the outer object when the outer object still has ref count of zero, cause an error. To guard this, a pair of AddRef()/Release() is added around the creation of inner object (also on page 96), and is named “stabilization”.

 

The ATL source code followed this in the code for CComCreator, and debugging show that InternalAddRef() and InternalRelease() are called.

 

However, question is on the necessity of stabilization:

 

Line 8263 onwards in atlbase.h (VS2022) shows a canonical implementation of QueryInterface(), in which AddRef() is only called when the query is successful. Otherwise the returns never equal S_OK (at line 8289 hRes is default initialized to something like 0xcccccccc rather than 0(==S_OK)).

 

Hence if QueryInterface() is unsuccessful, AddRef() will not be called, but also the return would not be S_OK, and the m_pTruck->Release() code in Don’s page 96 code would not be called either, so there is no danger of accidently calling Release() on the outer object when its ref count is zero.

 

Could you provide more insights on why the stabilization pairs is required? 

Customer

Microsoft 365 and Office | Development | Other
Developer technologies | C++
Developer technologies | .NET | Other
{count} votes

Accepted answer
  1. RLWA32 49,541 Reputation points
    2024-04-29T13:58:59.2466667+00:00

    In Inner::Initialize(), which QueryInterface() and saves the interface pointer once and for all, and Release() the extra count.

    If the reference count of the outer object is 0 when FinalConstruct is entered Inner::Initialize() will increment the outer object reference count to 1 and then Release() will decrement the count to 0. Inside the outer object's Release() the reference count of 0 will trigger object deletion (i.e., delete this).

    ATL's implementation of stabilization will increment the outer object reference count from 0 to 1 before the call to Inner::Initialize(). So Inner::Initialize() will then increment the outer object reference count to 2 and then outer object's Release() decrements it back to 1 and so avoids object destruction.

    1 person found this answer helpful.
    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. masterjodi 40 Reputation points
    2024-04-29T13:36:18.4933333+00:00

    Hello,

    "If all of this took place when the outer object's reference count was initially zero then":

    Assume outer.refcount==0,

    in Don Box book p.96 Inner::Initialize(void):

    1. If QueryInterface succeeds, outer.refcount==1, so later it cannot get accidentally destroyed.
    2. If QueryInterface fails, HRESULT hr != S_OK, so m_pTruck->Release() cannot get executed.

    where would "decrementing the reference count would result in the object's destruction" happen?


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.