Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

The Sad Story of CoGetMalloc

The Sad Story of CoGetMalloc

  • Comments 11

Ok, I admit it.  I'm a geek.

I'm actually one of those people who rants over the loss of the printed version of the platform SDK documentation (I still have some of the original Win32 Platform SDK documents in my office and use them regularly).

One of the things I used to LOVE doing is to take the old platform SDK documents, there was all SORTS of stuff that you could find in them.  It was just FUN to read through and look at all the cool APIs people had come up with.

 

If you look through the COM documentation, you'll find a bunch of rather fascinating entries (I started this article with the comment that I'm a geek, right?).

COM describes a bunch of memory allocation routines - there's CoTaskMemAlloc, CoTaskMemFree,  CoTaskMemRealloc.  There are even APIs to allow you to spy on allocations (CoRegisterMallocSpy and the IMallocSpy interface).  This makes sense - as a part of the contract for any interface, the allocation strategy associated with that interface needs to be documented, COM encapsulates the allocation strategy in the CoTaskMem* APIs.

There's also this rather odd duck of an API called CoGetMalloc.  CoGetMalloc returns a pointer to an object that implements the IMalloc interface.  And it turns out that the CoTaskMemAlloc, CoTaskMemFree and CoTaskMemRealloc routines are really just procedural wrappers around the IMalloc interface.  If you think about it, this is just wierd.  Why on earth would there be a function that allows you to retrieve a pointer to an interface that duplicates other functionality?  It's not likely it's for efficiency.

It turns out that way back when - so far back that it's almost in prehistoric times (we're talking Win3.1 here), the COM memory allocator operated on a per-apartment basis.  It was possible to replace the default allocator for a particular apartment, and that allocator would be used for all subsequent COM calls.  Obviously you can't perform such a substitution on-the-fly, so you had to establish the allocator when you created the apartment in the first place.  And that's what the first (now reserved) parameter to the CoInitialize and CoInitializeEx API used to do - it was a pointer to an IMalloc object which would subsequently be associated with the apartment.  In Win3.1, that first parameter was a pointer to an IMalloc object.

At some point after this, it was determined that having the ability to provide an application specific allocator was a bad idea and the functionality was removed - I'm not sure why, but it was.

After the ability to set an allocator was removed, there was no need for an API to retrieve the allocator (after all, since there's only one allocator, you're always going to get the same answer).  But of course, since it's been a documented API since COM was first created, it lives on.  It's the COM version of the human appendix - a sad remainder of a time long gone that has outlived its original purpose.

  • ... of course, in the case where you need to give a block of memory allocated with the task allocator to someone else, it's still useful -- you need some way of telling that other code how to deallocate the block. And conversely, if you want memory from someone else to give to COM APIs which want memory allocated with the task allocator, it makes sense that there be some way of providing that other code with an allocator.

    Not that there's anything stopping you writing an IMalloc implementation which defers to CoTask* but still -- it's not completely useless.

  • ???  The COM contract says that memory is allocated with CoTaskMemAlloc and freed with CoTaskMemFree.  

    There's no need to hand them the result of CoGetMalloc, because they can simply call CoTaskMemFree to free the memory.  This was true in Win 3.1, it's still true.

  • I taught myself to program by reading the printed manuals. My bedtime reading in middle school included the Turbo Pascal language guide and the Windows API documentation. I liked being able to flip through the pages to stumble on functions that looked interesting. The next morning, I would see whether I could write a program that used those functions, or incorporate those functions into a program I'd written before.

  • Hm.. I've also enjoyed reading the Platform SDK just to see what kind of functionality is in there. Sometimes later if I need such functionality I'll vaguely recall that it exists and can go back there and reread it and use it.

  • Here is another good article on this:

    http://blogs.msdn.com/oldnewthing/archive/2004/07/05/173226.aspx

  • As i read this, it occurred to me that i'd once used IMalloc in some code that needed to free shell-allocated pIDLs. No idea where i originally saw the method i use documented, but since i first used it it's found its way into a number of programs. With a sinking feeling in my heart, i brought up the MSDN page for SHGetMalloc()...

    "This function should no longer be used. Use the CoTaskMemFree and CoTaskMemAlloc functions in its place."

    D'oh. :-(

    I can definitely see where sitting down with a printed book and flipping through pages might have been beneficial here...

  • Yesterday I talked about CoGetMalloc . One thing I didn't include was why the ability to specify an allocator

  • Larry, you missed my point. Suppose I have an API which wants to be given an allocator in order to customize its behaviour. In much the same way STL container classes do. It makes some sense to use IMalloc as the interface by which it communicates with its allocator. It's not unreasonable to want to give it an allocator which uses the C++ heap, and it's not unreasonable to give it an allocator which uses the CoTask* functions. And if it has some interface whereby you can pass ownership of memory blocks into/out of it, you'll definitely want to give it CoGetMalloc() if those blocks should be allocated/freed by the COM task allocator. Isn't that obvious?

  • Richard, I'm with you for the first sentence - that's the way COM originally worked (the IMalloc provided the allocator).  

    But I don't understand your 3rd sentence.  What does "some interface whereby you can pass ownership of memory blocks" mean?  Do you mean that the API needs to define it's allocation semantics?  That's a given for all APIs (the allocation semantics are a critical part of an APIs contract).  You don't need a COM interface to do that however.

    You can never marshal an IMalloc across a boundary (machine, process,  or apartment), because the semantics of IMalloc are purely local.

  • Larry -- what I mean by the third sentence is that the API I'm thinking of doesn't define its allocation semantics; the user of the API chooses the allocation semantics by specifying an allocator, and the user of the API specifies the allocator by means of passing in an IMalloc interface.

    Here's a fictitious API based around something in my company's codebase:

    //! Create a packet manager.

    PMHANDLE PacketManager_Create(IMalloc *pMalloc, DWORD dwFlags);

    //...

    //! Release ownership of the memory for a packet to the caller.

    void *PacketManager_Release(PMHANDLE hMgr, PACKETID id);

    Suppose we want to pass a packet to a COM function, which expects memory to be allocated with CoTaskMemAlloc. Rather than reallocating and copying the memory, we call PacketManager_Release, which returns us a block of memory allocated with the allocator we gave to PacketManager_Create. We can then hand off ownership of that memory to said COM function.

  • Ok, that makes more sense - you're essentially saying that IMalloc can be used as an abstraction layer to allow the consumer of an API to specify allocation semantics to a producer API.  It's an interesting concept, and similar to the one that COM tried.  

    COM's version failed because the applications that tried it didn't always implement the required semantics for IMalloc (they used heaps that weren't multithread safe, for example).  This kind of pattern moves a large amount of the responsibility for the correctness of an interface outside of the interface, which can be fragile (I'm not saying it's wrong, just that it can be fragile).

Page 1 of 1 (11 items)