Using IMallocSpy to check for BSTR memory leaks in Embarcadero C++ Builder CB2009

IMallocSpy is a COM interface provided to check for memory leaks using the IMalloc interface, as used in allocating BSTR’s. I was recently trying to figure out which CB2009 objects would release the returned BSTR, so I looked for a way to test for memory leaks. I found this reference for using IMallocSpy using the Microsoft tools http://comcorba.tripod.com/comleaks.htm , but could not find it adapted for CB2009. Here is my adaptation for CB2009.

The answer to my original question:


OleVariant var = node->GetAttribute("xmlns"); // this DOES NOT cause a memory leak = OleVariant takes control of the BSTR, and then frees it
UnicodeString str = (wchar_t*)node->GetAttribute("xmlns"); // this DOES cause a memory leak
WideString ws = node->GetAttribute("xmlns"); // this DOES NOT cause a memory leak
WideString nws = node->GetAttribute("nonesuch"); // this returns a NULL, which throws an exception

If the attribute might not exist and you don’t want to throw an exception, you can use:


String str;
OleVariant val = subNode->Attributes[desiredSubAttribute];
if (!val.IsNull())
str = val;

Here is the code to implement the IMallocSpy tester. It allocates an array to keep track of IMalloc allocations and deallocations, and then dumps its output with OutputDebugString when requested. For details, see the link above. Again, the code below is ported from the example code at comcorba.tripod.com by Jason Pritchard.

As noted, do NOT include this code in software sent to a customer. It is for testing only.


// Need to call SetOaNoCache to turn off the BSTR Cache, or else we will ALWAYS report leaks

typedef void WINAPI (*SETOANOCACHE)();

HINSTANCE hDLL = LoadLibrary(L”oleaut32.dll”);
if (!hDLL)
throw Exception(“Unable to load oleaut32.dll”);

SETOANOCACHE SetOaNoCachePtr = (SETOANOCACHE) GetProcAddress(hDLL, “SetOaNoCache”);
if (!SetOaNoCachePtr) {
throw Exception(“Unable to get SetOaNoCache”);
}
SetOaNoCachePtr();

// Initialize COM.
::CoInitialize(NULL);

// Initialize the COM memory checker …
CMallocSpy* pMallocSpy = new CMallocSpy;
pMallocSpy->AddRef();
::CoRegisterMallocSpy(pMallocSpy);

pMallocSpy->Clear();

// pMallocSpy->SetBreakAlloc(4); // enable this if you want the debugger to break at COM allocation 4

test_com_allocs(); // run your test allocations and deallocations – e.g. the test code above

// Dump COM memory leaks
pMallocSpy->Dump();

// Unregister the malloc spy …
::CoRevokeMallocSpy();
pMallocSpy->Release();
::CoUninitialize();

The COM memory checker object:


// IMallocSpyUnit.h
class CMallocSpy : public IMallocSpy
{
public:
CMallocSpy(void);
~CMallocSpy(void);

// IUnknown methods
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject);

virtual ULONG STDMETHODCALLTYPE AddRef( void);

virtual ULONG STDMETHODCALLTYPE Release( void);

// IMallocSpy methods
virtual SIZE_T STDMETHODCALLTYPE PreAlloc(
/* [in] */ SIZE_T cbRequest);

virtual void *STDMETHODCALLTYPE PostAlloc(
/* [in] */ void *pActual);

virtual void *STDMETHODCALLTYPE PreFree(
/* [in] */ void *pRequest,
/* [in] */ BOOL fSpyed);

virtual void STDMETHODCALLTYPE PostFree(
/* [in] */ BOOL fSpyed);

virtual SIZE_T STDMETHODCALLTYPE PreRealloc(
/* [in] */ void *pRequest,
/* [in] */ SIZE_T cbRequest,
/* [out] */ void **ppNewRequest,
/* [in] */ BOOL fSpyed);

virtual void *STDMETHODCALLTYPE PostRealloc(
/* [in] */ void *pActual,
/* [in] */ BOOL fSpyed);

virtual void *STDMETHODCALLTYPE PreGetSize(
/* [in] */ void *pRequest,
/* [in] */ BOOL fSpyed);

virtual SIZE_T STDMETHODCALLTYPE PostGetSize(
/* [in] */ SIZE_T cbActual,
/* [in] */ BOOL fSpyed);

virtual void *STDMETHODCALLTYPE PreDidAlloc(
/* [in] */ void *pRequest,
/* [in] */ BOOL fSpyed);

virtual int STDMETHODCALLTYPE PostDidAlloc(
/* [in] */ void *pRequest,
/* [in] */ BOOL fSpyed,
/* [in] */ int fActual);

virtual void STDMETHODCALLTYPE PreHeapMinimize( void);

virtual void STDMETHODCALLTYPE PostHeapMinimize( void);

// Utilities …

void Clear();
void Dump();
void SetBreakAlloc(int allocNum);

protected:
enum
{
HEADERSIZE = 4,
MAX_ALLOCATIONS = 100000 // cannot handle more than max
};

ULONG m_cRef;
ULONG m_cbRequest;
int m_counter;
int m_breakAlloc;
char *m_map;
size_t m_mapSize;
};

// IMallocSpyUnit.cpp
#include // for IUnknown, IMallocSpy, etc.
#include “IMallocSpyUnit.h”

#pragma package(smart_init)

// Constructor/Destructor

CMallocSpy::CMallocSpy(void)
{
m_cRef = 0;
m_counter = 0;
m_mapSize = MAX_ALLOCATIONS;
m_map = new char[m_mapSize];
memset(m_map, 0, m_mapSize);
}

CMallocSpy::~CMallocSpy(void)
{
delete [] m_map;
}

// IUnknown support …

HRESULT STDMETHODCALLTYPE CMallocSpy::QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppUnk)
{
HRESULT hr = S_OK;
if (IsEqualIID(riid, IID_IUnknown))
{
*ppUnk = (IUnknown *) this;
}
else if (IsEqualIID(riid, IID_IMallocSpy))
{
*ppUnk = (IMalloc *) this;
}
else
{
*ppUnk = NULL;
hr = E_NOINTERFACE;
}

AddRef();
return hr;
}

ULONG STDMETHODCALLTYPE CMallocSpy::AddRef( void)
{
return ++m_cRef;
}

ULONG STDMETHODCALLTYPE CMallocSpy::Release(void)
{
ULONG cRef;
cRef = –m_cRef;
if (cRef == 0)
{
delete this;
}

return cRef;
}

// Utilities …
void CMallocSpy::SetBreakAlloc(int allocNum)
{
m_breakAlloc = allocNum;
}

void CMallocSpy::Clear()
{
memset(m_map, 0, m_mapSize);
}

void CMallocSpy::Dump()
{
char buff[256];
::OutputDebugString(“CMallocSpy dump ->n”);
for (int i=0; i {
if (m_map[i] != 0)
{
sprintf(buff, ” IMalloc memory leak at [%d]n”, i);
::OutputDebugString(buff);
}
}
::OutputDebugString(“CMallocSpy dump complete.n”);
}

// IMallocSpy methods …
SIZE_T STDMETHODCALLTYPE CMallocSpy::PreAlloc(
/* [in] */ SIZE_T cbRequest)
{
m_cbRequest = cbRequest;
return cbRequest + HEADERSIZE;
}

void *STDMETHODCALLTYPE CMallocSpy::PostAlloc(
/* [in] */ void *pActual)
{
m_counter++;
if (m_breakAlloc == m_counter)
::DebugBreak();
// Store the allocation counter and note that this allocation is active in the map.
memcpy(pActual, &m_counter, 4);
m_map[m_counter] = 1;
return (void*)((BYTE*)pActual + HEADERSIZE);
}

void *STDMETHODCALLTYPE CMallocSpy::PreFree(
/* [in] */ void *pRequest,
/* [in] */ BOOL fSpyed)
{
if (pRequest == NULL)
return NULL;

if (fSpyed)
{
// Mark the allocation as inactive in the map.
int counter;
pRequest = (void*)(((BYTE*)pRequest) – HEADERSIZE);
memcpy(&counter, pRequest, 4);
m_map[counter] = 0;
return pRequest;
}
else
return pRequest;
}

void STDMETHODCALLTYPE CMallocSpy::PostFree(
/* [in] */ BOOL fSpyed)
{
return;
}

SIZE_T STDMETHODCALLTYPE CMallocSpy::PreRealloc(
/* [in] */ void *pRequest,
/* [in] */ SIZE_T cbRequest,
/* [out] */ void **ppNewRequest,
/* [in] */ BOOL fSpyed)
{
if (fSpyed && pRequest != NULL)
{
// Mark the allocation as inactive in the map since IMalloc::Realloc()
// frees the originally allocated block.
int counter;
BYTE* actual = (BYTE*)pRequest – HEADERSIZE;
memcpy(&counter, actual, 4);
m_map[counter] = 0;
*ppNewRequest = (void*)(((BYTE*)pRequest) – HEADERSIZE);
return cbRequest + HEADERSIZE;
}
else
{
*ppNewRequest = pRequest;
return cbRequest;
}
}

void *STDMETHODCALLTYPE CMallocSpy::PostRealloc(
/* [in] */ void *pActual,
/* [in] */ BOOL fSpyed)
{
if (fSpyed)
{
m_counter++;
if (m_breakAlloc == m_counter)
::DebugBreak();

// Store the allocation counter and note that this allocation
// is active in the map.
memcpy(pActual, &m_counter, 4);
m_map[m_counter] = 1;
return (void*)((BYTE*)pActual + HEADERSIZE);
}
else
return pActual;

}

void *STDMETHODCALLTYPE CMallocSpy::PreGetSize(
/* [in] */ void *pRequest,
/* [in] */ BOOL fSpyed)
{
if (fSpyed)
return (void *) (((BYTE *) pRequest) – HEADERSIZE);
else
return pRequest;
}

SIZE_T STDMETHODCALLTYPE CMallocSpy::PostGetSize(
/* [in] */ SIZE_T cbActual,
/* [in] */ BOOL fSpyed)
{
if (fSpyed)
return cbActual – HEADERSIZE;
else
return cbActual;
}

void *STDMETHODCALLTYPE CMallocSpy::PreDidAlloc(
/* [in] */ void *pRequest,
/* [in] */ BOOL fSpyed)
{
if (fSpyed)
return (void *) (((BYTE *) pRequest) – HEADERSIZE);
else
return pRequest;
}

int STDMETHODCALLTYPE CMallocSpy::PostDidAlloc(
/* [in] */ void *pRequest,
/* [in] */ BOOL fSpyed,
/* [in] */ int fActual)
{
return fActual;
}

void STDMETHODCALLTYPE CMallocSpy::PreHeapMinimize( void)
{
return;
}

void STDMETHODCALLTYPE CMallocSpy::PostHeapMinimize( void)
{
return;
}

Leave a Reply

Your email address will not be published. Required fields are marked *