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);
}
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)
{
}
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;
}