Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Interop - Releasing memory allocated in unmanaged code

Tags:

c#

interop

I'm calling the following VC++ method

__declspec(dllexport) unsigned char* Get_Version_String()

from C# as follows:

internal static class NativeMethods
{
    [DllImport("my.dll"),
        CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, 
        CallingConvention = CallingConvention.Cdecl)]
    internal static extern string Get_Version_String();
}

The above code is in a library which targets .NET 3.5. When I call this from a 3.5 assembly, it works fine; when calling it from a 4.5 assembly, however, it results in

0xC0000374: A heap has been corrupted

After reading this question, I changed my method calls as follows:

[DllImport("my.dll",
    EntryPoint = "Get_Version_String",
    CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, 
    CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr Get_Version_String_PInvoke();

internal static string Get_Version_String()
{
    IntPtr ptr = Get_Version_String_PInvoke();
    string versionString = Marshal.PtrToStringAnsi(ptr);
    return versionString;
}

This works as expected, but the answer from Hans Passant comes with a warning:

The workaround you found is the correct one, the marshaller isn't going to try to release the memory for an IntPtr. Do note that this will only actually come to a good end if the C code returns a const char* that doesn't need to be released. You have a permanent memory leak if that's not the case.

Since the C++ method does not return const, I'm going on the assumption that the workaround for my specific function will result in a memory leak.

I cannot change the original method so I found this other question which discusses how to free memory from manged code. However, calling either Marshal.FreeHGlobal(ptr) or Marshal.FreeCoTaskMem(ptr) also throw 0xC0000374: A heap has been corrupted.

Can anyone
a) confirm that such a method will indeed suffer from a memory leak, and
b) if so, suggest how to free the memory from the pointer in managed code?

The C++ method body is, simplified, as follows:

unsigned char versionString[50];

__declspec(dllexport) unsigned char* Get_Version_String()
{
    strcpy((char *) versionString, "Key1:[xx],Key2:[xx],Key3:[xx],Key4:[xx]");
    // string manipulation
    return versionString;
}

Thanks in advance, and sorry if this is trivial; I'm neither a C++ nor an Interop expert.

like image 591
user5877732 Avatar asked Mar 02 '16 12:03

user5877732


1 Answers

Can anyone confirm that such a method will indeed suffer from a memory leak

Only you can do that, the missing const keyword is not a guarantee that the native code does not in fact return a literal. Pervasive mistake in C code btw. Write a little test program that calls the function a hundred million times. If you don't see the memory usage explode with Task Manager then you don't have a problem.

if so, suggest how to free the memory from the pointer in managed code?

You just can't, it must be the native code itself that calls free(). So that it uses the correct heap, the one that was created by the C runtime library used by that code. Underlying winapi call is HeapCreate(), you don't have the heap handle. Technically it is discoverable with GetProcessHeaps() but you just don't know which one is the "right" one. Starting with VS2012, the CRT uses GetProcessHeap() instead of HeapCreate(), now Marshal.FreeHGlobal() can work. But you know that this code doesn't, you'll have to ask the author or vendor for an update. As long as you do that, ask him for a more usable flavor of this function, it should take a char* as an argument instead.


A more constructive approach is to just take the memory leak in stride. Simply call the function once, the version number is not going to change while your program is running. So store it in a static variable. Losing ~80 bytes of address space is not a problem you can ever notice, the OS automatically cleans up when your program terminates.

like image 81
Hans Passant Avatar answered Nov 03 '22 21:11

Hans Passant