Kim Gräsman, August 2004
I hang out in newsgroups a lot, and one of the most common questions is this [paraphrased]:
I've built this ATL component, and I'm calling it from VB. It works great, but I've noticed whenever I use one of its string properties more than once, the thing just crashes! This makes me hate COM, but I've heard COM is all about love... What to do?
As it happens, this always boils down to the same category of bugs — misunderstanding COM's rules for resource management.
By resource management, I refer to keeping track of ownership of either memory or object references. The COM specification covers this in section 1.2.1, but honestly, who reads the specification? There are wizards, for pete's sake!
One might wonder what makes COM so important it needs to dictate a memory allocation and ownership policy. Of course, it would be convenient to be able to decide for yourself if you want your clients to allocate a buffer for you, or if you want to allocate it yourself, just like in the olden days. Or would it?
I personally find the stringency refreshing — it's liberating to be able to read memory management semantics directly from a method signature.
If you're not as easily charmed, remember that COM is ultimately a binary specification, a protocol for distributed objects. COM objects can be consumed transparently across process- and machine boundaries, and from a multitude of languages. In order to do all that work for you, COM reserves the right to control the protocol for memory allocations. Consider the example below.
[...]
interface IFortuneTeller : IUnknown
{
HRESULT PredictYourFuture([in] BSTR bstrName, [out, retval] BSTR* pFuture);
HRESULT Predict([out] BSTR* pFuture);
HRESULT ModifyPrediction([in, out] BSTR pPrediction);
}
A situation where a client calls the PredictYourFuture method is illustrated below, showing the journey of a call from caller through proxy and stub to the callee.
Let's walk through what happens in the process called marshalling, i.e. the vaporization and materialization of parameters happening in the proxy/stub pair:
bstrName parameter to the proxybstrName in the callee's process spacebstrNamepFuture, and returnsbstrName and *pFuture*pFuture in the caller's process space*pFuture, so it points to the newly allocated stringbstrName and *pFutureThe bold-marked steps mark the places where the COM run-time makes assumptions about the way memory is allocated. If there were no stead-fast policy for it, the proxy- and stub implementations could not be hard-wired, and it would be very difficult to remote COM interfaces in an efficient manner. Further to that, mix multiple languages into the picture, and it would be next to impossible to maintain custom resource management policies without a very rich metadata layer to describe them, and lots of logic to cover for it.
Unfortunately, COM does not (and cannot) enforce this policy, other than live by it. So, if you don't play by the rules something's going to break, but not necessarily your code. The proxy, stub and client code are all free to make assumptions about how a server should act with regards to resource ownership.
Consider for a second what would happen if everybody could pick their own way of allocation — ultimately it would lead to the same kind of problems as if we were to skip the ownership/direction rules I've described above.
So, it is mandated that all general-purpose memory crossing various COM boundaries (be it process, machine or programming language) must be allocated using CoTaskMemAlloc. There are two other ways of allocating COM-approved memory; the SysAllocString family for BSTRs and the SafeArrayCreate family for SAFEARRAYs. Both of these, however, fall back on the same underlying allocator as CoTaskMemAlloc, so they basically only help with the layout of the respective types in memory.
The standard marshalling code available makes assumptions about allocator based on type. All BSTRs are assumed to use SysAllocString, all SAFEARRAYs SafeArrayCreate, and all other pointer types use the generic allocator CoTaskMemAlloc. Also, COM client languages where memory management is hidden (Visual Basic, et al.) are bound by the specification to use the various designated allocators to guarantee that memory is allocated and deallocated as expected.
Note that COM interface pointers are not affected by this allocator rule-set, since objects are never allocated in that sense. CoCreateInstance delegates to a class object that allocates an object somehow (e.g. operator new), and AddRef and Relese take care of the details as to when the object will be deallocated. When the reference count hits zero, the object pulls itself out of existence by deallocating itself (e.g. delete this). There's never a need for anybody but the object and its factory to know how it was actually allocated.
Following below is a simple table showing everything you need to know about COM resource management. Please note that allocation and deallocation below is synonymous with AddRef and Release for interface pointers.
| Direction | Responsible for allocation | Responsible for deallocation |
|---|---|---|
| [in] | Caller | Caller |
| [in, out] | Caller | Caller and/or Callee |
| [out] | Callee | Caller |
| [out, retval] | Callee | Caller |
Note that [retval] doesn't affect the responsibilities in the slightest — it's just a signal for client languages to treat the parameter like a return value, if they can. It's still [out].
That's it. If you make sure to follow these rules for parameter passing, you'll be fine, provided that the COM party you're talking to does the right thing as well.
The table above is fairly clear, except for the [in, out] case, which looks ambiguous. If you think about it, it isn't that strange, though. When an [out] parameter is marshalled across a process boundary, no care is taken to transport the original value from the caller context to the callee context. It is assumed to be irrelevant, since the callee overwrites it anyway.
However, for [in, out] parameters, we care about the value coming in, and so it is transported from caller to callee. If the callee wishes to modify the value, they need to reallocate it accordingly.
Compare the methods below, where Predict is plain [out], and ModifyPrediction is [in, out] and has logic for reallocating the string depending on some conditional.
STDMETHODIMP CFortuneTeller::Predict(/*[out]*/ BSTR* pFuture)
{
if(pFuture == NULL) return E_POINTER;
// Make a prediction
*pFuture = ::SysAllocString(L"Caller will free this string");
return S_OK;
}
STDMETHODIMP CFortuneTeller::ModifyPrediction(/*[in,out]*/ BSTR* pPrediction)
{
HRESULT hr = S_FALSE;
if(pPrediction == NULL) return E_POINTER;
if(*pPrediction)
{
if(::SysStringLen(*pPrediction) < 45)
{
// Never mind what I said before...
::SysFreeString(*pPrediction);
// ... Re-predict!
*pPrediction = ::SysAllocString(L"I can feel it - caller WILL free this string!");
hr = S_OK;
}
else
{
// Let the original value fall through
}
}
else
{
// Make that prediction
hr = Predict(pPrediction);
}
return hr;
}
The client is still responsible for freeing when finished with it, we've just added the ability for the server to reallocate the value at will.
I want to issue one word of warning, though: Do not attempt to use [in, out] or [out]-only with Visual Basic, VBScript or JScript clients! Reasons covered here and here. And it's even more complex than that, so just don't do it.
I've attempted to describe why the COM resource management rules are necessary and what they mandate. I have also covered the apparent ambiguity of [in, out] rules.
Hopefully, all this will make it easier for you to diagnose and avoid memory problems in your COM applications. If you disagree with any of my conclusions or think that I've missed anything, don't hesitate to contact me.