Memory Management

Earlier in this chapter, we defined rules for handling out-parameters and in/out-parameters that deal with memory allocation. We learned that OLE specifies that the caller of a function become responsible for the memory involved with those parameters. We also learned that in order to make this work, there has to be a standard allocation mechanism accessible to both object and client so that memory can pass freely between them, even across processes. This mechanism is COM's task memory allocation service, based on the memory management APIs of the underlying system.

The service is provided through an allocator object that supports the single interface IMalloc (which includes IUnknown functions, of course). All components are required to use this service whenever there is a need to exchange allocated memory between components, even across process boundaries.9 Any code in a task accesses this service by calling the API function CoGetMalloc as follows:


HRESULT    hr;
IMalloc *pIMalloc;

hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);
//Use IMalloc functions through pIMalloc.
pIMalloc- >Release();

CoGetMalloc is a function that returns a new interface pointer, so pIMalloc has a reference count on it when you get the pointer back; therefore, remember to call Release when you finish. (You must always pass MEMCTX_TASK as the first argument.)

So what can you do with an allocator? Everything is represented through the IMalloc interface, which is defined as follows:


interface IMalloc : IUnknown
{
void * Alloc(ULONG cb);
void * Realloc(void *pv, ULONG cb);
void Free(void *pv);
ULONG GetSize(void *pv);
int DidAlloc(void *pv);
void HeapMinimize(void);
};

I'll leave it to your intelligence to guess what Alloc, Realloc, Free, GetSize, and HeapMinimize do, mentioning only that all allocations are referred to with a pointer, just as with the C run-time malloc functions. The one function to describe a little further is DidAlloc, which returns 1 if the memory was allocated by the allocator in question, 0 if the memory was not, and -1 if the allocator simply doesn't know.

With this service, any piece of code can, at any time, get the task allocator's IMalloc pointer, allocate or free memory, and call IMalloc::Release. You may be required to free out-parameters and in/out-parameters in this way as a client. Usually this can be something of a pain to code, having to call three functions to perform a simple operation. For this reason, OLE includes three helper functions, CoTaskMemAlloc, CoTaskMemFree, and CoTaskMemRealloc, which take the same parameters as the interface functions and internally call CoGetMalloc, the appropriate IMalloc function, and then IMalloc::Release.

9 When cross-process allocations are involved the marshaling layer in COM takes care of copying object-task allocations into client-task allocations.