Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ
October 1995


Code for this article: activexcode1095.exe (6KB)
Don Box is a co-founder of DevelopMentor where he manages the COM curriculum. Don is currently breathing deep sighs of relief as his new book, Essential COM (Addison-Wesley), is finally complete. Don can be reached at http://www.develop.com/dbox/default.asp.

Q When does the task allocator need to be used? Is it unsafe to use my compiler's built-in operator new?
Frequently Asked Question

A The task allocator has a very well-defined role in COM. To understand it, you first need to look at the problem that it solves. When a function needs to return a variable-length data structure to the caller (such as a text string or a variable-length array), at least two methods are feasible. The classic approach in Windows® has been for the caller to decide the maximum size allowed and pass a fixed-length buffer to the function along with its size. Consider the following interface:


 interface IShortList {
   void AppendShort(short val);
   int GetAllShorts(short *rgs, long cMaxElems);
 };
This would be used as follows:

 short array[256];
 int nActual = pSL->GetAllShorts(array, 256);
 MyPrintShorts(array, nActual);
As long as GetAllShorts has 256 or fewer shorts to give, this style of API works fine. But what happens when the caller really wants to get all of the shorts available? To support this, the interface needs to provide a second method to discover the actual length of the array:

 interface IShortList2 {
   void AppendShort(short val);
   int GetAllShorts(short *rgs, long cMaxElems);
   int GetShortCount();
 };
This implies that the caller will first find out the required buffer size, allocate a buffer, and then get the elements from the object:

 int nSize = pSL->GetShortCount();
 short *array = new short[nSize];
 int nActual = pSL->GetAllShorts(array, nSize);
 MyPrintShorts(array, nActual);
 delete[] array;
This style of interface is flawed in two ways: it is possible for the object to receive a call to AppendShort between the calls to GetShortCount and GetAllShorts, and the client now needs to make two method calls to perform one operation. The first flaw could be addressed by adding a Lock and Unlock method to the interface, requiring the caller to now make four method calls to correctly fetch all of the shorts. This would be a really bad idea, since it exacerbates the second (and more fundamental) flaw with this interface, that is, excessive round trips between the caller (the client) and the object.
For a normal DLL-based API (like Win32
®), the performance penalty for making two or four calls instead of one is slight. For a COM interface, however, making excessive method calls could bring the system to a grinding halt. The overhead for invoking a method on an out-of-process object can be 10,000 times greater than calling methods on in-process objects. When Network OLE becomes a reality, this already considerable overhead will increase substantially (unless Microsoft is able to alter the speed of light). Thus, you must keep the number of method calls required to perform an operation as low as possible (hopefully as low as one). One way to achieve this is for the object to take the responsibility for allocating all variable-length data structures, based on the assumption that allocating memory is a cheaper operation than a client-object round trip.
By requiring the object to allocate the memory for the array, you can simplify the IShortList interface, as follows:

 interface IShortList3 {
   void AppendShort(short val);
   int GetAllShorts(short **rgs);
 };
 
 short *array;
 int nActual = pSL->GetAllShorts(&array);
 MyPrintShorts(array, nActual);
Only one method invocation is necessary, and the client gets all of the shorts available at the time of the call.
Now—how does the client release the memory used to store the array? Using operator delete[] would certainly be incorrect, as delete potentially invokes destructors and often uses a C run-time library function to deallocate the memory. Another approach would be to have the interface indicate in its documentation which Win32 API call to use (such as HeapFree or GlobalFree), but this implies that your code will only run under Windows, in addition to imposing special constraints on the proxy/stub pairs used in standard marshaling (in essence, the stub is a client to the object, the proxy acts as the server to the client). To reduce platform-dependence and to encourage uniformity, COM introduces its own memory allocation API in the form of the task allocator.
The task allocator is a COM object that exists in your address space automatically. The task allocator provides an implementation of the IMalloc interface:

 interface IMalloc : IUnknown {
   void *Alloc(ULONG cb);
   void  Free(void *pv);
   void *Realloc(void *pv, ULONG cbNewSize);
   ULONG GetSize(void *pv);
   int   DidAlloc(void *pv);
   void  HeapMinimize();
 };
These methods correspond to the old _fmalloc library calls used in antique Windows; under Win32, the current implementation uses the HeapAlloc API to manage its memory. Clients and objects can access the task allocator for an address space by calling CoGetMalloc:

 IMalloc *pMalloc;
 if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pMalloc)))
 {
   LPTSTR p = LPTSTR(pMalloc->Alloc(1024));
   lstrcpy(p, __TEXT("I am on the heap")); 
   pMalloc->Release();
 }
In 32-bit OLE, there are three convenience functions that access the task allocator without explicitly using its IMalloc interface:

 void *CoTaskMemAlloc(ULONG cb);
 void *CoTaskMemRealloc(void *pv, ULONG cbNewSize);
 void  CoTaskMemFree(void *pv);
These functions can be used interchangeably with calls through the IMalloc interface.

 // client code (using CoTaskMemFree)
 short *array;
 int nActual = pSL->GetAllShorts(&array);
 MyPrintShorts(array, nActual);
 CoTaskMemFree(array);
 // object code (using CoGetMalloc/IMalloc)
 int CoMyList::GetAllShorts(short **rgs)
 {
   IMalloc *pm;
   CoGetMalloc(MEMCTX_TASK, &pm);
   *rgs = pm->Alloc(m_size * sizeof(short));
   memcpy(*rgs, m_shorts, m_size * sizeof(short));
   return m_size;
 }
Since the proxies and stubs generated by the MIDL compiler also use the task allocator, the code above will work properly even for out-of-process objects (although the memory will be freed in the object's process after the stub transmits the result packet to the client).
There is a popular misconception that you must override your compiler's built-in operator new to use the task allocator. This is patently false. Replacing the global operator new is a really bad idea, as some class libraries (MFC for one) provide their own implementations of new already. Even in the absence of MFC, your development environment is already providing you with a perfectly usable implementation (probably based on HeapAlloc). Remember, the only reason you must use the task allocator is for passing raw blocks of memory to external agents to free. COM places no constraints on the memory that you consume for your own internal use or for heap-based COM objects that expose interfaces to external clients (it would be a really bad move to call delete or CoTaskMemFree on an interface pointer). Although the current implementation of the BSTR and SAFEARRAY APIs (both used in OLE automation) use the task allocator as their memory allocator of choice, even these data types could have been allocated using some other technique provided the proxy and stub implementations were aware of it.

Q Is there any way for me to register my own allocator for debugging purposes?

Dren McDonald
Long Beach, CA

A No and yes. No, you cannot register your own allocator to replace the default task allocator. Yes, you can track all calls to the task allocator for debugging. Let's look at the no part first, since it's simpler to answer.
Under 16-bit Windows, it is possible to register your own implementation of IMalloc to use as the task allocator by passing a pointer to your allocator object to CoInitialize or OleInitialize:


 HRESULT CoInitialize(IMalloc FAR *pMalloc);
 HRESULT OleInitialize(IMalloc FAR *pMalloc);
When Microsoft moved OLE to 32 bits under Windows NT 3.5, they removed the ability to register your own allocator, changing the prototypes for these functions:

 HRESULT CoInitialize(void *pvReserved);
 HRESULT OleInitialize(void *pvReserved);
Prohibiting user-defined allocators solved an important problem relating to DLLs. It is possible that an implicitly linked DLL that makes use of OLE would need to call CoInitialize or CoGetMalloc in its initial entry point (LibMain or DllMain). These entry points are called prior to WinMain, making it a really bad idea for the application to replace the allocator that was in place when the DLL was loaded. Simply prohibiting user-defined allocators guarantees that all calls to CoGetMalloc will return the same object.
Now for the yes part of the answer. To allow users to track allocations, Microsoft added support for hooking the task allocator in Windows NT
3.51 and Windows® 95. COM now supports the standard interface shown in Figure 1. Each IMalloc interface method has two methods in IMallocSpy, one that is called prior to IMalloc's normal implementation and one that is called after. Here's a simplified version of the default task allocator's Alloc method:

 void *CoStdAlloc::Alloc(ULONG cbSize)
 {
 // allow malloc spy to adjust size
   if (g_pMallocSpy)
     cbSize = g_pMallocSpy->PreAlloc(cbSize);
 // allocate memory
   void *result = HeapAlloc(g_hheap, 0, cbSize);
 // allow malloc spy to adjust result pointer
   if (g_pMallocSpy)
     result = g_pMallocSpy->PostAlloc(result)
   return result;
 }
Each PreMethod receives the parameters passed by the user on input and is given the opportunity to modify them prior to the actual implementation of the method. Once the method performs the work, the PostMethod is called indicating the result that will be passed to the user, again, allowing the malloc spy to modify the values. Figure 2 shows a simple implementation of IMallocSpy called CoHeapDetective. CoHeapDetective tracks the total number of bytes allocated and can optionally send trace information to an I/O device or file. It prepends an arena header in each allocated block to record its allocation size. This header also contains a guard signature (0x1BADABBA) that is used to detect corrupted blocks.
In addition to defining the IMallocSpy interface, COM also provides routines for installation and deinstallation of the spy:

 HRESULT CoRegisterMallocSpy(IMallocSpy* pms);
 HRESULT CoRevokeMallocSpy();
Registering a MallocSpy after allocations have already taken place is not a problem. The real implementation of the task allocator marks all allocated blocks to indicate whether or not they were allocated while a spy was registered. Note that all IMalloc methods that receive a pointer as a parameter have corresponding IMallocSpy PreMethods that also receive a Boolean indicating the spied-on status of the block. This Boolean can be used by the spy to selectively ignore the blocks that were allocated prior to registration.
Figure 4
Figure 4

Figure 3 and Figure 4 illustrate a program that sporadically leaks memory or writes over memory that doesn't belong to it (simulating the vast majority of software currently developed by humans). By registering an IMallocSpy, these aspects of the program can be discovered (and optionally repaired) during the development phase of the application.

Have a question about programming with ActiveX or COM? Send your questions via email to Don Box at dbox@develop.com or http://www.develop.com/dbox/default.asp

From the October 1995 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe .

© 1997 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2016 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft