Home » Articles » Best Practices »COM »The Basics »Tutorials » p6COM Reference Counting: A Primer

p6COM Reference Counting: A Primer

By Jim Susoy - April 27, 2014 @ 8:49 pm

Introduction

Reference counting generally comes in two basic flavors: intrusive and non-intrusive. Non-intrusive reference counting basically means that the location of the variable which stores the reference value is outside of the “thing” being reference counted. COM interfaces make use of intrusive reference counting, meaning the underlying object maintains the reference count value itself internally. The purpose of having a reference count on a COM interface is to ensure that the lifetime of the underlying object includes the lifetimes of all the references to it.  P6COM follows the standard COM reference counting model, so if you are already familiar with COM reference counting, this should all be review. There are also many good references elsewhere on this topic (including section 3.3.2 of the official COM reference), this post is meant as a quick guide to COM reference counting rules, not an exhaustive treatise.

A Word On Smart Pointers

Most implementations of COM include smartptr’s to manage reference counting, and p6COM is no different in this regard. I strongly encourage you to use p6ComPtr<> to manage your interface pointers (or whichever smartptr your COM implementation provides). COM smart pointers reduce/remove interface leaks and reduce the amount of code that you need to write. They have huge benefits, please use them. See A Smart Pointer for p6COM for a small tutorial. That being said, you should still have a clear understanding of the rules involved.

The Rules

To be successful, it’s very important to understand exactly when to call addref() and release().  The following rules define a policy which must be consistently applied:

Rule 1 – Always call addref() on a new copy of an interface pointer and release() when it is no longer needed, except as the following rules specifiy.

In general, whenever you create or make a copy of an interface pointer you must call addref() on the pointer before you use it, and release() on the pointer when you no longer need it.

Rule 2 – Always addref() before returning an interface pointer to the caller.

You must ensure that when returning an interface pointer (directly or through an out parameter) it has a an additional reference count applied before returning. If the callee synthesizes the pointer, it must call addref():

P6ERR CSomeClass::createInstance(ISomeIface **ppIface)
{
   CSomeClass  *pSomeClass = NULL;

   if(!ppIface) return eInvalidArg;

   pSomeClass = new CSomeClass;
   (*ppIface) = static_cast<ISomeIface*>(pSomeClass);
   (*ppIface)->addref();      // addref() before returning

   return err;
}

If the callee makes a copy of an internal interface pointer, then addref() must be called.

P6ERR CSomeClass::createInstance(ISomeIface **ppIface)
{
   if(!ppIface) return eInvalidArg;

   (*ppIface) = m_pSomeIface; // An interface pointer stored internally in our object
   (*ppIface)->addref();      // addref() before returning
   return eOk;
}

It important to note that it is the callers (the one receiving the new interface pointer) responsibility to release() the interface when it is done using it. This is the thing that smart pointers are great at managing for you. No matter how diligent you try to be you will forget to do this at some point causing an interface leak. Smart pointers can ensure that this never happens.

Rule 2a – Exception: callee uses another method to create the interface pointer.

If the callee called another method which created the pointer, then it will have called addref() and no additional reference is needed.

P6ERR CSomeClass::createInstance(ISomeIface **ppIface)
{
   return someFactory(ppIface); // someFactory() will addref() before returning. 
                                // We just pass it up to the caller. Calling addref()
                                // again here would cause an interface leak.
}

As you can see it does not matter where the interface pointer came from, if you return it directly or through and out parameter, addref() must be called first.

Rule 3 – Call addref() when making a local copy of an interface pointer stored in a global variable.

A much better solution to this is not to use global variables. In a single threaded environment, called methods could release the global thereby invalidating the interface pointer in the local copy. You must call addref() before making any other calls. In a multi-threaded environment (assuming the interface itself if thread-safe), you must obtain the local copy and call addref() under a lock to ensure that it remains valid long enough to grab the reference. Once addref() has been called, the lock may be released and the interface used. You must call release on the local copy when you are done.

Rule 4 – addref() and release() need not be called on in parameters

The callee knows that by definition the interface pointer passed to it as an “in” parameter has a greater lifetime than itself. Therefore, the callee should not call addref() or release() on any “in” parameter.

P6ERR CSomeClass::someMethod(ISomeIface *pIface)
{
   // no addref()
   pIface->someOtherMethod();
   // No release()
   return eOk;
}

Rule 4a – Exception: callee stores interface pointer for later use.

An exception to this is if the callee makes a copy if the interface pointer for use at a later time (storing the copy internally or globally), addref() must called on the copy (and release once it is no longer needed).

P6ERR CSomeClass::someMethod(ISomeIface *pIface)
{
   m_pSomeIface = pIface;   // Save interface pointer for later use
   m_pSomeIface->addref();  // Make sure it sticks around till we need it
                            // We are now responsible for calling release()
                            // on m_pSomeIface when it is no longer needed.

   pIface->someOtherMethod();
   return eOk;
}

Rule 5 – addref() in-out parameters on the way in and out

This is somewhat of a special case and I urge you to not use in-out parameters of any type because they complicate things which leads to defects. When an interface pointer is passed via an “in-out” parameter, the caller must addref() the interface pointer on the way in (if it expects it to still be valid after the call) because the callee will need to release() it before storing the out-value on top of it. The callee then treats the out parameter as in Rule 2 above. It’s important to understand that the callee must always release() the incoming interface pointer, even if it is not overwritten with a new/different interface.

P6ERR CSomeClass::amethod(ISomeIface **ppInOutIface)
{   
   CSomeClass *pClass = NULL;

   (*ppInOutIface)->someMethod();  // Make use of the incoming interface
   (*ppInOutIface)->release();     // Release the incoming interface 
   (*ppInOutIface) = NULL;         // Good form to prevent anything from dangling

   if(NULL != (pClass = new CSomeClass)) {
      (*ppInOutIface) = static_cast<ISomeIface*>(pClass);
      (*ppInOutIface)->addref();
   }
   return eOk;
}

Here’s another example which calls another factory class to create the outbound interface and so does not have to addref() the interface pointer before returning.

P6ERR CSomeClass::aMethod(ISomeIface **ppInOutIface)
{
   (*ppInOutIface)->someMethod();  // Make use of the incoming interface
   (*ppInOutIface)->release();     // Release the incoming interface 
   (*ppInOutIface) = NULL;         // Good form to prevent anything from dangling

   // CSomeClass::createInstance() is a theoretical factory method 
   // which creates and returns an instance of ISomeIface. Following
   // Rule 2 it calls addref() on the interface pointer before 
   // returning it. Therefore we don't need to here and we pass it
   / back to the caller.
   return CSomeClass::createInstance(ppInOutIface); 
}

This should cover the majority of situations that you come across and the official documentation (and the plethora of books on COM) should handle the rest.

"p6COM Reference Counting: A Primer" was published on April 27th, 2014 and is listed in Best Practices, COM, The Basics, Tutorials.

Follow comments via the RSS Feed | Leave a comment | Trackback URL


Leave Your Comment