Free unmanaged memory allocation from managed code

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

Code C:

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

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 show that the garbage collector does not free memory allocated by C code.

Any help would be appreciated. :)

+6
memory-management dllimport unmanaged
source share
6 answers

The managed string does not match char *. What happens undercover is that the interaction-level marshaling code makes a copy of the unmanaged string to convert it to a managed string, but it cannot free this memory because it does not know how it was assigned.

However, you can try allocating and returning BSTR instead of char *. The level of interaction is better associated with automation types than classic unmanaged data types.

The reason this matters is how char * and BSTR are allocated in memory.

Char * buffers are allocated on the heap of the C ++ runtime using private allocation / deallocation procedures that the CLR knows nothing, so there is no way to delete this memory. And to make matters worse, the buffer that char * points to can be allocated by the internal implementation of the dll code heap, or it can even point to a member variable in a private class.

On the other hand, BSTRs are allocated using the SysAllocString WIndows API and freed by SyFreeStirng, and since the CLR interoperability level about these APIs Windows knows how to free BSTR from unmanaged code.

+6
source share

The P / Invoke router will assume that the memory for the return type has been allocated by CoTaskMemAlloc () and will call CoTaskMemFree () to free it. If this was not done, the program will fail with exception in Vista and Win7, but memory will leak without problems on XP. Using SysAllocString () can be done to work, but you must annotate the return type in the [DllImport] attribute. Not doing this will cause a leak anyway, without diagnostics on Win7. BSTR is not a pointer to the memory block allocated by CoTaskMemAlloc, there are 4 bytes in front of the specified address that store the size of the string.

Any of the following combinations will work correctly:

 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 that there are no problems with memory management. It should look something like 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(); 
+7
source share

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

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

 IntPtr ptr = Marshal.AllocHGlobal(100 * sizeof(char)); SomeUnmanagedFunc(ptr); Marshal.FreeHGlobal(ptr); 
+6
source share

Another way to do this is to pass a managed string (an instance of StringBuilder) through P / Invoke (as a parameter to your Run function).

Thus, no distributions are performed on the unmanaged side.

In other words, you will have something like:

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

and name it as follows:

 [DllImport("Unmanaged.dll", CharSet = CharSet.Ansi)] static extern void Run(StringBuilder result); StringBuilder result = new StringBuilder(100); Run(result); 
+3
source share

I read some questions about PInvoke and I stayed here. I don’t know if the problem still really applies to you, but I decided to post my answer to future readers.

This is your last comment on Darin Dimitrov. When the size of the allocated memory is unknown, a typical solution is to call an unmanaged function with a null pointer and get the size in the output parameter. Then we allocate the necessary space and call the unmanaged function again.

Example 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; } 
+2
source share

.NET memory MUST be allocated within the CLR to clear the GC. You need to add a function to free a block in the C DLL.

Remember to free memory in the same C DLL instance that created the memory. You cannot mix and match.

-one
source share

All Articles