The COM marshaller uses the COM task allocator to allocate and free memory


It should be second nature to you that the code which allocates memory and the code which frees memory need to use the same allocator. Most of the time, you think of it as "If you allocate memory, you need to free it with the corresponding mechanism," but this sentence works in the reverse direction as well: If you hand memory to a function that will free it, you have to allocate the memory with the corresponding mechanism.

Let's look at this question that appeared on a discussion group:

I have the following method defined in my IDL file:

HRESULT GetSomething([in, string] LPCWSTR pszArg,
                     [out] DWORD* pcchOut, 
                     [out, size_is(, *pcchOut)] LPWSTR* ppszOut);

My server implementation of this method goes like this:

STDMETHODIMP CSomething::GetSomething(
    LPCWSTR pszArg, DWORD* pcchOut, LPWSTR* ppszOut)
{
    HRESULT hr = ...
    DWORD cch = ...

    *pcchOut = cch;
    *ppszOut = new(nothrow) WCHAR[cch];
    // ... fill in *ppszOut if successful ...

    return hr;
}

When I call this method from a client, the COM server crashes after CSomething::GetSomething returns. What am I doing wrong?

The answer should be obvious to you, particularly given the hint in the introductory paragraph, but for some reason, the people on the discussion group got all worked up about how the annotations on the ppszOut parameter should have been written, whether *pcchOut is a count of bytes or WCHARs, how the marshaller was registered, and nobody even noticed that the allocator didn't match the deallocator.

The rule for COM is that any memory that one module allocates and another module frees must use the COM task allocator. The intent of this rule is to set down one simple, straightforward rule; without it, everybody would have to create their own mechanism for allocating and freeing memory across module boundaries, resulting in the same mishmash that we have in plain Win32, with the global heap, the local heap, the process heap, the C runtime library, or even ad-hoc explicitly paired memory allocation functions like NetApiBufferAllocate and NetApiBufferFree.

Instead, with COM, it's very simple. If you allocate memory that another COM component will free, then you must use CoTaskMemAlloc* and if you free memory that another COM component allocated, then you must use CoTaskMemFree.*

In this case, the CSomething::GetSomething method is allocating memory that the calling component will eventually free. Therefore, the memory must be allocated with CoTaskMemAlloc.*

Nitpicker's corner

*Or a moral equivalent. Note that SysAllocString is not a moral equivalent to CoTaskMemAlloc.

Remark: MSDN can't seem to make up its mind whether to double the L at the end of "marshal" before adding a suffix, so when searching for information about marshalling, try it both ways.

Comments (17)
  1. porter says:

    > SysAllocString is not a moral equivalent to CoTaskMemAlloc.

    As far as I can determine, SysAllocString does use CoTaskMemAlloc but stores the length of the string at the start and returns you the pointer to the string immediately following the length. SysFreeString must presumably decrement by 4 then CoTaskMemFree. So you can’t mix and match API, but they have come from the same pool.

  2. Luke says:

    /me takes a look, shudders, returns to the warm embrace of .NET

  3. Mike Dunn v2.0 says:

    porter> The implied advice is "if the caller will be freeing with CoTaskMemFree, don’t return memory you allocated with SysAllocString."

  4. michael says:

    @porter: The reason SysAllocString stores the string length was covered here: http://blogs.msdn.com/ericlippert/archive/2003/09/12/52976.aspx

  5. MadQ says:

    I apologize if this is considered a moral equivalent, but I think that it’s kind of an oddball… if your code ever comes across a STGMEDIUM structure, I recommend reading the documentation for ReleaseStgMedium very thoroughly.

    The STGMEDIUM/ReleaseStgMedium mechanism is quite useful, but you should be very clear about who is supposed to free (or release) the medium. I’ve caused a memory leak or two by getting it wrong.

  6. I have to agree with Luke: it certainly has been nice not having to think about this stuff nearly as much since .NET and C# came along.

  7. Jonathan Pryor says:

    @Nawak:

    The COM task memory allocator IS required, because it has one requirement that none of the pre-existing memory allocators (HeapAlloc, LocalAlloc, malloc, etc.) could support: cross-process allocation and freeing.

    Due to the joy of DCOM, it’s entirely possible for process A (on machine X) to allocate memory and send it to process B (on machine Y).  No pre-existing allocator (that I know of) will support this, and this is required for DCOM to actually be Distributed.

  8. ulric says:

    I cannot understand how people cannot know this, but then again I learned though the book OLE 2.0, and as far as I can see 99% of programmer seem to just copy/paste/hack as they go along.  No one learns.

    CoTaskMemAlloc (or rather, IMalloc) is not really about allocating cross-process, it doesn’t allocate shared memory. It probably just maps to LocalAlloc underneath.

    It’s about having a platform-neutral API that provides 1) alloc 2) free 3) get block size,

    all three of which are required by a marshaller.

    The OLE API 2.0 is cross platform and is not supposed to be dependent on the Win32, that’s why it has its own allocation API, and a language neutral one.

    It doesn’t allocate shared memory, there is no shared memory on Windows.

    Something that marshals between processes or machine can get the task allocator on one side, get the size of the block with 3) and copy the date in a shared location, then get the allocator on the other side and copy the block back in there.  And now the client on the other side can safely free that memory with his task allocator – even if he’s using a customized allocator.

    [Good point. A lot of people forget that you can do COM on a Mac, and there is no LocalAlloc on OS X… That’s one of the reasons for the macro dance you have to do when writing COM interfaces by hand. -Raymond]
  9. Ian Boyd says:

    OK, can someone hit me over the head with it? Where is the deallocator?

    I’m pretty sure I can point out the allocator; even though it’s in a language a don’t know.

  10. Nawak says:

    Maybe it will be an incredibly stupid remark as I don’t know anything about COM.

    Does this CoTaskMemAlloc really solves a problem?

    Why is this different from the “mishmash that we have in plain Win32″? It really looks like Yet Another Memory Allocator from where I stand (far from the subject remember)

    Ok, defining what allocator is *the* COM allocator was really needed, but couldn’t it have been one of the already existing ones?

    It wouldn’t change the moral of the story here, except that there would be one less allocator in the wild.

    It seemed to me that if the returned block of memory had any special property other than “must be freed with CoTaskMemFree”, maybe more people on the mailing list would have noticed. “Dude! Memory you allocate with ‘new’ is too coarse and insufficiently lubricated! It can’t pass module boundaries man!!”

    [See IMallocSpy. -Raymond]
  11. Drak says:

    @Ian:

    eventhough I have never programmed with COM before (i think), the idea is that you should know what the de-allocator is, because of the agreement that COM always uses CoTaskMemAlloc and CoTaskMemFree.

    So, implicitly the de-allocator is CoTaskMemFree, because that’s what was agreed upon when entering the world of COM.

    I think Raymod didn’t add the de-allocator because the people on the mailing list didn’t know what it was either (they sent the memory to some 3rd pary component maybe).

  12. Mark says:

    Ian Boyd: in the client, code not shown.

  13. Neil says:

    Wikipedia has an interesting article on differences between US and UK spelling, but unfortunately "marshalling" isn’t specifically listed. Many Wikipedia articles, e.g. Travelling salesman problem, have redirects from the wrong^H^H^H^H^H alternate spelling.

  14. Dean Harding says:

    Jonathan Pryor: They way cross-process allocation works is that the marsheller in the client frees the memory after it sends it to the server, then the marsheller in the server allocates it in that other process. The memory isn’t "magically" shared between the two (consider the case where client and server are on different machines).

    With that in mind, you still *could* use an existing memory allocation strategy. The reason CoTaskMemAlloc exists is as Raymond said: IMallocSpy (though COM could’ve been done without IMallocSpy, obviously).

  15. cashto says:

    My understanding has always been that the reason CoTaskMemAlloc exists was strictly for the in-proc case.  If my COM object is served out of DLL 1 and consumed by DLL 2, I can’t make the assumption that we’re both using the same implementation of new and delete.  

    *Most* of the time new just calls into LocalAlloc but it’s possible that DLL 2 could have been compiled against a custom heap implementation.  Thus the need to agree on allocator and deallocator.

  16. porter says:

    > A lot of people forget that you can do COM on a Mac, and there is no LocalAlloc on OS X

    I had a look at COM on the Mac. There are two competing implementations, that provided by the module "Microsoft OLE", where mine is CFM and that done by rolling your own using the CFPlugIn architecture. The later one depends on any plug-in respecting the CFAllocator it was given at factory creation time. So can the real Mac CoTaskMemAlloc stand up?

  17. wcoenen says:

    Maybe it’s because I’m not a native speaker, but I’m confused by the repeated use of the expression “moral equivalent” here. I was similarly confused when I read the post on SHCIDS_CANONICALONLY.

    http://www.google.be/search?q=define%3A+moral+equivalent

    http://en.wikipedia.org/wiki/Moral_equivalent

    [Moral equivalence = equivalent in a moral sense. I am obviously using the term metaphorically and not literally. -Raymond]

Comments are closed.