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

Embarcadero C++ Builder – Sending UnicodeString via COM truncates string to half

CB2009 defines a BSTR as a wchar_t*, which is the type returned by c_str() when UNICODE is set. Therefore, if you call a COM function which expects a BSTR with UnicodeString.c_str(), the compiler returns the wchar_t* which the function expects. Sounds good! Except it doesn’t work!!

Actually, both BSTR and the character array in UnicodeString prefix the array of wide chars with a four-byte integer that gives the length. However, BSTR expects this to be the length IN BYTES, whereas UnicodeString makes this the length IN CHARACTERS. Thus, the server gets the wchar_t*, looks right before the pointer for an int, and only uses that many bytes. So, if your UnicodeString is seven chars long, and thus 14 bytes long, CB2009 sets the length to seven, and COM only accepts the first seven bytes (and thus the first four chars). So, all your strings are cut in half!

To make things worse, UnicodeString.Length() does not count out the length to the null terminating char, but rather just returns the integer. So, if you fix the integer to be 14 as COM expects, UnicodeString.Length now returns 14 for your seven character string! We dare not mess with UnicodeString’s data.

The solution is to use WideString instead. Instead of:

UnicodeString myString = L”My Data”;
ptr->ComFunctionExpectingBSTR(myString.c_str()); // this compiles, but COM only uses the first half of the string

use

UnicodeString myString = L”My Data”;
ptr->ComFunctionExpectingBSTR(WideString(myString).c_bstr()); // this has the correct length

References:
http://msdn.microsoft.com/en-us/library/ms221069.aspx – Microsoft’s reference for the BSTR type in MSDN/Win32 and COM Development/Component Development/COM/Automation Programming Reference/Data Types, Structures and Enumerations/IDispatch Data Types and Structures

http://docwiki.embarcadero.com/RADStudio/en/Unicode_in_RAD_Studio#New_String_Type:_UnicodeString – Unicode in RAD Studio

NOTE: In my testing, this is a problem sending data to Microsoft Outlook 2007. It did not appear to cause a problem when sending to Crystal Reports, so the issue might well be with the server code rather than the COM subsystem, depending on how the server determines the length of the passed string. I have seen example code that just passes a WideString to COM, but for me, the compiler gives a Type mismatch error unless I pass the pointer returned by c_bstr()

To see how the length of the string is set:

wchar_t* lit = L"My Sttring";
int* litIPtr = (int*) lit;
litIPtr--;
int litLen = *litIPtr;

WideString ws = lit;
wchar_t* wsPtr = ws.c_bstr();
int* wsIPtr = (int*) wsPtr;
wsIPtr--;
int wsLen = *wsIPtr;

UnicodeString us = lit;
wchar_t* usPtr = us.c_str();
int* usIPtr = (int*) usPtr;
usIPtr--;
int usLen = *usIPtr;

// int actualLen = StrLen(lit); // if UNICODE is set
int actualLen = wcslen(lit);

ShowMessage("For a " + String(actualLen) + " character string, Literal gives " +
String(litLen) + ", WideString gives " +
String(wsLen) + ", UnicodeString gives " + String(usLen));