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. :)
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.
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.
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.
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.
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();
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);
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);
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With