Figure 1   ImallocSpy


 interface IMallocSpy : IUnknown {
   ULONG PreAlloc(ULONG cbRequest);
   void* PostAlloc(void *pActual);
 
   void* PreFree(void *pRequest, BOOL fSpyed);
   void  PostFree(BOOL fSpyed);
 
   ULONG PreRealloc(void *pRequest,
                    ULONG cbRequest, 
                    void **ppNewRequest, 
                    BOOL fSpyed);
   void* PostRealloc(void *pActual, BOOL fSpyed);
 
   void* PreGetSize(void *pRequest, BOOL fSpyed);
   ULONG PostGetSize(ULONG cbActual, BOOL fSpyed);
 
   void* PreDidAlloc(void *pRequest, BOOL fSpyed);
   int   PostDidAlloc(void *pRequest, 
                      BOOL fSpyed, int fActual);
 
   void  PreHeapMinimize(void);
   void  PostHeapMinimize(void);
}


Figure 2   HEAPDET

HEAPDET.H


 /////////////////////////////////////////////////////
 //
 // HeapDet.h - 1995, Don Box
 //
 // Simple IMallocSpy to track allocation byte count
 //
 
 #ifndef _HEAPDET_H
 #define _HEAPDET_H
 
 class CoHeapDetective : public IMallocSpy
 {
 public:
 // this exception will be thrown if a bad ptr is detected
   class XCorruptedHeap {};
 
   CoHeapDetective(HANDLE hTrace = 0);
   virtual ~CoHeapDetective();
 
   DWORD GetBytesAlloced() const;
 
 private:
 // paramters to cache between pre/post phases
   long m_cbLastAlloc;
   void *m_pvLastRealloc;
 
 // total heap usage
   DWORD m_dwBytesAlloced;
 
 // output device for tracing
   HANDLE m_hTraceOutput;
 
 // helper function to send simple trace message to debug window
   void Trace(DWORD cb, LPCTSTR szAction, BOOL bSuccess);
 
 // simple alloc header to track allocation size
   struct ArenaHeader
   {
     enum { SIGNATURE = 0x1BADABBAL };
     DWORD m_dwAllocSize;  // the user's idea of size
     DWORD m_dwSignature;  // always 0x1BADABBA when good
   };
 
 // helper function to write a valid arena header at ptr
   void SetArenaHeader(void *ptr, DWORD dwAllocSize);
 
 // helper function to verify and return the prepended
 // header (or null if failure)
   ArenaHeader *GetHeader(void *ptr);
 
 // IUnknown methods
 
   STDMETHODIMP QueryInterface(REFIID riid, void**ppv);
   STDMETHODIMP_(ULONG) AddRef();
   STDMETHODIMP_(ULONG) Release();
 
 // IMallocSpy methods
 
   STDMETHODIMP_(ULONG) PreAlloc(ULONG cbRequest);
   STDMETHODIMP_(void*) PostAlloc(void *pActual);
 
   STDMETHODIMP_(void*) PreFree(void *pRequest, BOOL fSpyed);
   STDMETHODIMP_(void)  PostFree(BOOL fSpyed);
 
   STDMETHODIMP_(ULONG) PreRealloc(void *pRequest,  ULONG cbRequest,
                                   void **ppNewRequest, BOOL fSpyed);
   STDMETHODIMP_(void*) PostRealloc(void *pActual, BOOL fSpyed);
 
   STDMETHODIMP_(void*) PreGetSize(void *pRequest, BOOL fSpyed);
   STDMETHODIMP_(ULONG) PostGetSize(ULONG cbActual, BOOL fSpyed);
 
   STDMETHODIMP_(void*) PreDidAlloc(void *pRequest, BOOL fSpyed);
   STDMETHODIMP_(int)   PostDidAlloc(void *pRequest, BOOL fSpyed, int fActual);
 
   STDMETHODIMP_(void)  PreHeapMinimize(void);
   STDMETHODIMP_(void)  PostHeapMinimize(void);
 
 };
 
#endif

HEAPDET.CPP


 //////////////////////////////////////////////////////
 //
 // HeapDet.cpp - 1995, Don Box
 //
 // Simple IMallocSpy to track allocation byte count
 //
 
 #include <windows.h>
 #include "HeapDet.h"
 
 // initialize data members
 CoHeapDetective::CoHeapDetective(HANDLE hTrace)
 : m_cbLastAlloc(0),
   m_pvLastRealloc(0),
   m_dwBytesAlloced(0),
   m_hTraceOutput(hTrace)
 {
 }
 
 CoHeapDetective::~CoHeapDetective()
 {
   Trace(m_dwBytesAlloced, __TEXT("left on the heap"), TRUE);
 }
 
 // return the current byte count
 DWORD
 CoHeapDetective::GetBytesAlloced() const
 {
   return m_dwBytesAlloced;
 }
 
 // optionally write trace statement to output device
 void
 CoHeapDetective::Trace(DWORD cb, LPCTSTR szAction, BOOL bSuccess)
 {
   if (m_hTraceOutput)
   {
     TCHAR buf[256];
     DWORD dw;
     wsprintf(buf, __TEXT("%u bytes were %s%s\n"), cb, szAction,
              (bSuccess ? __TEXT(".") : __TEXT("...NOT!")));
 
     WriteFile(m_hTraceOutput, buf, lstrlen(buf) * sizeof(TCHAR), &dw, 0);
   }
 }
 
 // write signature and alloc size
 void
 CoHeapDetective::SetArenaHeader(void *ptr, DWORD dwAllocSize)
 {
   if (ptr)
   {
     ArenaHeader& arena = *((ArenaHeader *)ptr);
     arena.m_dwSignature = ArenaHeader::SIGNATURE;
     arena.m_dwAllocSize = dwAllocSize;
   }
 }
 
 // return pointer to valid header or null
 CoHeapDetective::ArenaHeader *
 CoHeapDetective::GetHeader(void *ptr)
 {
   ArenaHeader *result = 0;
   if (ptr)
   {
     result = (ArenaHeader *)(LPBYTE(ptr)-sizeof(ArenaHeader));
     if (result->m_dwSignature != ArenaHeader::SIGNATURE)
       throw XCorruptedHeap();
   }
   return result;
 }
 
 // IUnknown Methods ////////////////////
 
 STDMETHODIMP
 CoHeapDetective::QueryInterface(REFIID riid, void**ppv)
 {
   if (riid = = IID_IUnknown || riid = = IID_IMallocSpy)
     LPUNKNOWN(*ppv = (IMallocSpy*)this)->AddRef();
   else
     *ppv = 0;
   return ResultFromScode(*ppv ? S_OK : E_NOINTERFACE);
 }
 
 //
 // this object is global and doesn't hold server, so punt
 //
 STDMETHODIMP_(ULONG)
 CoHeapDetective::AddRef()
 {
   return 2;
 }
 
 STDMETHODIMP_(ULONG)
 CoHeapDetective::Release()
 {
   return 1;
 }
 
 // IMallocSpy Methods //////////////////
 
 //
 // PreAlloc reserves space for our arena header
 //
 STDMETHODIMP_(ULONG)
 CoHeapDetective::PreAlloc(ULONG cbRequest)
 {
 // cache user request for post processing
   m_cbLastAlloc = cbRequest;
 // reserve space for arena header
   return cbRequest + sizeof(ArenaHeader);
 }
 
 //
 // PostAlloc writes the arena header and updates the alloc count
 //
 STDMETHODIMP_(void*)
 CoHeapDetective::PostAlloc(void *pActual)
 {
   LPBYTE result = LPBYTE(pActual);
   Trace(m_cbLastAlloc, __TEXT("alloced"), pActual != 0);
   if (pActual) // alloc succeeded
   {
 // write arena header
     SetArenaHeader(pActual, m_cbLastAlloc);
 // adjust result
     result += sizeof(ArenaHeader);
 // tally allocation
     m_dwBytesAlloced += m_cbLastAlloc;
   }
   return result;
 }
 
 
 //
 // PreFree verifies the header, and adjusts the alloc count
 //
 STDMETHODIMP_(void*)
 CoHeapDetective::PreFree(void *pRequest, BOOL fSpyed)
 {
   LPBYTE result = LPBYTE(pRequest);
 
   if (pRequest && fSpyed) // it is definitely ours
   {
     ArenaHeader *phdr = GetHeader(pRequest);
 // adjust result
     result -= sizeof(ArenaHeader);
 // tally deallocation
     m_dwBytesAlloced -= phdr->m_dwAllocSize;
 
     Trace(phdr->m_dwAllocSize, __TEXT("freed"), TRUE);
   }
   return result;
 }
 
 //
 // PostFree gets called after the task allocator overwrites
 // our block, so there is very little we can do
 //
 STDMETHODIMP_(void)
 CoHeapDetective::PostFree(BOOL fSpyed)
 {
 }
 
 //
 // PreRealloc must verify the header, subtract the old size
 // from the alloc count, and cache the arguments for PostRealloc
 //
 STDMETHODIMP_(ULONG)
 CoHeapDetective::PreRealloc(void *pRequest, ULONG cbRequest,
                             void **ppNewRequest, BOOL fSpyed)
 {
   LPBYTE pNewRequest = LPBYTE(pRequest);
 
   if (fSpyed)  // it is ours
   {
 // cache the pointer for PostRealloc
     m_pvLastRealloc = pRequest;
     if (pRequest)  // genuine realloc
     {
       ArenaHeader *phdr = GetHeader(pRequest);
 // cache size
       m_cbLastAlloc = cbRequest;
 // adjust byte count
       m_dwBytesAlloced -= phdr->m_dwAllocSize;
 // adjust allocation to accomodate header
       cbRequest += sizeof(ArenaHeader);
       pNewRequest -= sizeof(ArenaHeader);
     }
     else  // call to realloc(0, size)
     {
 // cache size
       m_cbLastAlloc = cbRequest;
 // adjust allocation to accommodate header
       cbRequest += sizeof(ArenaHeader);
     }
   }
   *ppNewRequest = pNewRequest;
   return cbRequest;
 }
 
 //
 // Post realloc must adjust the alloc count and write the
 // new arena header if succeeded
 //
 STDMETHODIMP_(void*)
 CoHeapDetective::PostRealloc(void *pActual, BOOL fSpyed)
 {
   LPBYTE result = LPBYTE(pActual);
   if (fSpyed)  // it is ours
   {
     Trace(m_cbLastAlloc, __TEXT("realloced"), pActual != 0);
     if (pActual)   // realloc succeeded
     {
 // write arena header
       SetArenaHeader(pActual, m_cbLastAlloc);
 // adjust result
       result += sizeof(ArenaHeader);
 // tally allocation
       m_dwBytesAlloced += m_cbLastAlloc;
     }
     else        // realloc failed
     {
   // watch out for realloc(p, size) that fails (old block still valid)
       if (m_pvLastRealloc)
         m_dwBytesAlloced += GetHeader(m_pvLastRealloc)->m_dwAllocSize;
     }
   }
   return result;
 }
 
 //
 // PreGetSize simply needs to shear off and verify the header
 //
 STDMETHODIMP_(void*)
 CoHeapDetective::PreGetSize(void *pRequest, BOOL fSpyed)
 {
   LPBYTE result = LPBYTE(pRequest);
   if (fSpyed && pRequest && GetHeader(pRequest)) // it is ours and valid
     result -= sizeof(ArenaHeader);
   return result;
 }
 
 //
 // PostGetSize adjusts the size reported by sizeof(ArenaHeader)
 //
 STDMETHODIMP_(ULONG)
 CoHeapDetective::PostGetSize(ULONG cbActual, BOOL fSpyed)
 {
   return fSpyed ? (cbActual - sizeof(ArenaHeader)) : cbActual;
 }
 
 //
 // PreDidAlloc simply needs to shear off and verify the header
 //
 STDMETHODIMP_(void*)
 CoHeapDetective::PreDidAlloc(void *pRequest, BOOL fSpyed)
 {
   LPBYTE result = LPBYTE(pRequest);
   if (fSpyed && pRequest  && GetHeader(pRequest)) // it is ours and valid
     result -= sizeof(ArenaHeader);  // adjust result pointer
   return result;
 }
 
 //
 // PostDidAlloc is a no-op
 //
 STDMETHODIMP_(int)
 CoHeapDetective::PostDidAlloc(void *pRequest, BOOL fSpyed, int fActual)
 {
   return fActual;
 }
 
 //
 // PreHeapMinimize is a no-op
 //
 STDMETHODIMP_(void)
 CoHeapDetective::PreHeapMinimize(void)
 {
 }
 
 //
 // PostHeapMinimize is a no-op
 //
 STDMETHODIMP_(void)
 CoHeapDetective::PostHeapMinimize(void)
 {
}


Figure 3   LEAKMAIN

LEAKMAIN.CPP


 //////////////////////////////////////////////////////
 //
 // LeakMain.cpp - 1995, Don Box
 //
 // Task Allocator test app
 //
 
 #include <windows.h>
 
 #include "HeapDet.h"
 
 int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
 {
   CoInitialize(0);
 
 // create a trace file
   HANDLE hTrace = CreateFile(__TEXT("Logfile.txt"),
                               GENERIC_WRITE | GENERIC_READ,
                               FILE_SHARE_READ | FILE_SHARE_WRITE,
                               0,
                               CREATE_ALWAYS,
                               FILE_ATTRIBUTE_NORMAL,
                               0);
 
 // declare a heap detective object and register it
   CoHeapDetective sherlock(hTrace);
   SCODE scode = GetScode(CoRegisterMallocSpy(&sherlock));
   if (scode = = CO_E_OBJISREG)
      MessageBox(0, __TEXT("Someone has already registered a MallocSpy."),
                    __TEXT("Drat!!"), MB_OK);
 
   try
   {
 // allocate some raw memory and a BSTR
     void *p = CoTaskMemAlloc(128);
     BSTR bstr = SysAllocString(OLESTR("Hello, IMallocSpy"));
 
 // try to realloc the raw memory
     if (p)
     {
       void *temp = CoTaskMemRealloc(p, 128 * 2048);
       if (temp)
         p = temp;
     }
 
 // occasionally use a bad ptr, potentially corrupting the heap
     if (GetTickCount() & 0x100)
     {
       DWORD *pdw = LPDWORD(p) + 10;
       do
       *(--pdw) = 12;
       while (pdw >= LPDWORD(p)); // highly defective statement!
     }
 
 // free the raw memory
     if (p)
       CoTaskMemFree(p);
 
 // try to realloc the BSTR
     if (bstr)
       SysReAllocStringLen(&bstr, OLESTR("Goodbye, Im Al Locspy"), 256);
 
 
 
 // occasionally free the bstrs, potentially leaking
     if (GetTickCount() & 0x10)
       if (bstr)
         SysFreeString(bstr);
 
   }
   catch (const CoHeapDetective::XCorruptedHeap&)
   {
     MessageBox(0, __TEXT("The heap has been corrupted."),
                   __TEXT("The Ugly!"), MB_OK);
   }
 
   CoRevokeMallocSpy();
   CoUninitialize();
 
 // display the final status of heap
   DWORD cb = sherlock.GetBytesAlloced();
   if (cb)
   {
     TCHAR buf[80];
     wsprintf(buf, __TEXT("%u bytes were leaked from "
                           "the Task Allocator."), cb);
     MessageBox(0, buf, __TEXT("The Bad"), MB_OK);
   }
   else
     MessageBox(0, __TEXT("This application has a most hygenic heap."),
                   __TEXT("The Good"), MB_OK);
   return 0;
}