Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Free unmanaged memory allocation from managed code

A .NET application calls C dll. The C code allocates memory for a char array and returns this array as result. The .NET applications gets this result as a string.

The C code:

extern "C" __declspec(dllexport) char* __cdecl Run()
{
    char* result = (char*)malloc(100 * sizeof(char));
    // fill the array with data
    return result;
}

The C# code:

[DllImport("Unmanaged.dll")]
private static extern string Run();

...
string result = Run();
// do something useful with the result and than leave it out of scope

Some tests of it show that the garbage collector does not free the memory allocated by the C code.

Any help will be appreciated. :)

like image 889
Alex Avatar asked Dec 19 '09 08:12

Alex


People also ask

Can you explain managed code and unmanaged code?

Managed code is the one that is executed by the CLR of the . NET framework while unmanaged or unsafe code is executed by the operating system. The managed code provides security to the code while undamaged code creates security threats.

What is managed code and unmanaged code with example?

It gets the managed code and compiles it into machine code. After that, the code is executed. The runtime here i.e. CLR provides automatic memory management, type safety, etc. C/C++ code, called "unmanaged code” do not have that privilege.

Can garbage collector claim unmanaged objects?

So, the garbage collector is nothing but a background thread that runs continuously. Checks for unused managed objects clean those objects and reclaims the memory. Now, it is important to note that the garbage collector cleans and reclaims unused managed objects only. It does not clean unmanaged objects.


5 Answers

Managed string is not the same as char*. What happens undercover is that the marshaling code in the interop layer makes a copy of the unmanaged string in order to convert it to a managed string, but it can't free that memory as it does not know how it was allocated.

However, you could try allocating and returning a BSTR instead of a char*. The interop layer deals way better with automation datatypes than classic unmanaged data types.

The reason why that matters is the way char* and BSTRs are allocated in the memory.

The char* buffers are allocated on the heap of the C++ runtime using private allocation/deallocation routines that the CLR knows nothing about, so there's no way it can delete that memory. And to make things even worse, the buffer that char* points can be allocated by an internal heap implementation of the dll code, or it might even point to a member variable in a private class.

The BSTRs on the other hand are allocated using the WIndows API SysAllocString and are freed by SyFreeStirng and since the CLR interop layer knows about these Windows APIs, it knows how to free a BSTR it got from unmanaged code.

like image 124
Franci Penov Avatar answered Oct 16 '22 19:10

Franci Penov


The P/Invoke marshaller will assume that memory for the return type was allocated with CoTaskMemAlloc() and will call CoTaskMemFree() to release it. If this was not done, the program will fail with an exception on Vista and Win7 but silently leak memory on XP. Using SysAllocString() can be made to work but you have to annotate the return type in the [DllImport] attribute. Not doing so will still cause a leak, without a diagnostic on Win7. A BSTR is not a pointer to a memory block allocated by CoTaskMemAlloc, there are 4 bytes in front of the pointed-to address that store the string size.

Either of the following combinations will work properly:

extern "C" __declspec(dllexport)
BSTR __stdcall ReturnsAString() {
  return SysAllocString(L"Hello world");
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll")]
[return: MarshalAs(UnmanagedType.BStr)]   // NOTE: required!
private static extern string ReturnsAString();

Or:

extern "C" __declspec(dllexport)
const wchar_t* __stdcall ReturnsAString() {
  const wchar_t* str = L"Hello world";
  wchar_t* retval = (wchar_t*)CoTaskMemAlloc((wcslen(str)+1) * sizeof(wchar_t));
  wcscpy(retval, str);
  return retval;
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)]
private static extern string ReturnsAString();

You should consider allowing the client code to pass a buffer so there are no memory management issues. That ought to look similar to this:

extern "C" __declspec(dllexport)
void __stdcall ReturnsAString(wchar_t* buffer, size_t buflen) {
  wcscpy_s(buffer, buflen, L"Hello world");
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)]
private static extern void ReturnsAString(StringBuilder buffer, int buflen);
...
    StringBuilder sb = new StringBuilder(256);
    ReturnsAString(sb, sb.Capacity);
    string s = sb.ToString();
like image 35
Hans Passant Avatar answered Oct 16 '22 20:10

Hans Passant


You cannot free unmanaged memory from managed code. You need to write a routine in C that calls free on the pointer returned by the Run function and P/Invoke it from .NET.

Another option is to allocate unmanaged memory in .NET, pass the pointer to the C function which will fill it with data and finally free this pointer:

IntPtr ptr = Marshal.AllocHGlobal(100 * sizeof(char));
SomeUnmanagedFunc(ptr);
Marshal.FreeHGlobal(ptr);
like image 32
Darin Dimitrov Avatar answered Oct 16 '22 20:10

Darin Dimitrov


Another way to do it would be to pass a managed string (a StringBuilder instance) through P/Invoke (as a parameter to your Run function).

That way no allocations are made on the unmanaged side.

In other words, you would have something like:

extern "C" __declspec(dllexport) void __cdecl Run(char* data)
{
    // fill the array with data
    // no return value (void)
}

and call it like this:

[DllImport("Unmanaged.dll", CharSet = CharSet.Ansi)]
static extern void Run(StringBuilder result);

StringBuilder result = new StringBuilder(100);
Run(result);
like image 37
Groo Avatar answered Oct 16 '22 18:10

Groo


I was reading some questions about PInvoke and i stoped here. I don't know if the problem is still relevant to you but i decided to post my answer to future readers.

It's about your last comment to Darin Dimitrov's answer. When the size of allocated memory is not known, the tipical solution is to call the unmanaged function with a null pointer and receive the size in an out parameter. Then, we alloc the needed space and call again the unmanaged function.

Exemple below:

//MANAGED SIDE  
IntPtr ptr = IntPtr.Zero;  
int size = 0;  
myFunc(ptr, out size);  
ptr = Marshal.AllocHGlobal(size);  
myFunc(ptr, out size);  
//Do your work..  
Marshal.FreeHGlobal(ptr);  



//UNMANEGED SIDE  
int myFunc(void* dest, size_t& size){  
   if(dest==NULL)  
       //calculate de size..  
       size = 'resul'  
       return 0;  
    }  
    // create the array and copy all elements   
    memcopy(dest, ... , size);  
    //free all allocated space in unmanaged and return success  
    return 0;  
}  
like image 41
Zé Carlos Avatar answered Oct 16 '22 18:10

Zé Carlos