February 28, 2004

Yet another reason to love C#

This past week, a co-worker of mine tracked down a memory corruption bug in some C++ code of mine. The bug was that I had allocated memory using one of the numerous ways of allocating memory, but had failed to dispose of it using the matched method. To allocate something, you might use new, malloc, CoTaskMemAlloc, or QueryInterface (to get a COM object). The matched set of dispose operations would be delete, free, CoTaskMemFree, or Release. (I'm sure there are even more pairs -- those are just a few that I can think of off the top of my head.) Use the wrong dispose operation, and if you're lucky, the program will crash right away. In this case, of course, it didn't -- I had allocated a buffer with new, then it got disposed with CoTaskMemFree, and that worked most of the time. But when it did crash, the heap was corrupted for no obvious reason.

The cost of this bug? Days of QA being frustrated that the program crashed occasionally and trying (unsuccessfully) to find a reproducible case. More days for development to try to understand what was happening. More time for PMs fretting over the bug.

The bug was my fault, no doubt about that -- but why do I have to remember how I allocated something? Aren't computers supposed to keep track of this for me?

So, how does this work in C#? Simple: you don't ever even think about this. You allocate objects, and then later on, the garbage collector gets rid of them. How did you allocate the object? Doesn't matter.

And that's great. The computer knows how it was allocated, so it knows how to dispose of it. I don't have to think about it, which means that I can't screw it up. Since I can't screw it up, that's a bug that can't ever happen and won't cause us days of slip six months later in the project when the bug is finally triggered.

Posted by Mike at February 28, 2004 12:21 AM
Comments

Hi,

I have my method written in win32 dll with extern "C". I do pInvoke from C#. The dll allocates memory using CoTaskMemAlloc and in C# I get it as a string. I am not calling CoTaskMemFree as I do not know from where to call from C#. Finally when I close the dialog I get either Outofmemory exception or stackoverflow exception. One such exception message is: An unhandled exception of type 'System.OutOfMemoryException' occurred in mscorlib.dll.

How to overcome this problem. Here is the code similar to the one I have in my application.

I have written below the sample code. My actual application is some what similar to it.

In the code below I am allocating 1024 byte of memory for each value of i in the loop. But in the actual application this is a variable size.

Thank you so much for your assistance.
===========

Code Snippet:

in C#:

//Declaration & Definition
public struct User
{
public string m_strNameFirst;
public string m_strNameLast;
public double m_dAge;
}

[DllImport( "MyDLL.dll", SetLastError=true, ExactSpelling = true, CharSet = CharSet.Ansi)]
public static extern string GetUserList( [In, Out] User[] pUser, int iCount);

[DllImport( "MyDLL.dll", SetLastError=true, ExactSpelling = true, CharSet = CharSet.Ansi)]
public static extern string GetUserCount( ref int iCount);

public void understandPInvoke()
{
int iCount=0;
string strStatus = GetUserCount( ref iCount);

if( iCount User[] users = new User[iCount];
strStatus = GetUserList( users, iCount);
//when the user closes the dialog in where the above code exists, I copy this User structure name in my objects and use it in my application. I do not free any memory in my C# code.
}

C++ dll: (win32 dll):

struct CPP_User
{
char* pNameFirst;
char* pNameLast;
double dAge;
};

extern "C" MYDLL_API char* GetUserCount( int *pNumberOfUsers);
extern "C" MYDLL_API char* GetUserList( CPP_User pUsers[], int iCount);

char *PrepareStatus( char* pStatus)
{
char *strOutMsg = (char *)CoTaskMemAlloc( sizeof(char) * strlen( pStatus) + 1);
strOutMsg[0]=0;
strcpy( strOutMsg, pStatus);
return strOutMsg;
}

extern "C" MYDLL_API char* GetUserCount( int *pNumberOfUsers)
{
pNumberOfUsers = 99999;//This number can be huge but less than the max value of the int.
return PrepareStatus( "Success");
}


extern "C" MYDLL_API char* GetUserList( CPP_User pUsers[], int iCount)
{
for( int i=0; i {
CoTaskMemFree( pUsers[i].pNameFirst);
CoTaskMemFree( pUsers[i].pNameLast);

char *strName1 = (char *)CoTaskMemAlloc( sizeof(char) * 1024); //in my application this size is a variable one it will not be always 1024.
char *strName2 = (char *)CoTaskMemAlloc( sizeof(char) * 1024);
sprintf( strName1, "%s %i", "George", i);
sprintf( strName2, "%s %i", "Bush", i);

pUsers[i].pNameFirst = strName1;
pUsers[i].pNameLast = strName2;

pUsers[i].dAge = 30;
}

return PrepareStatus( "Success");
}

Posted by: Ganapathi Raman on March 31, 2005 08:51 AM