pinvoke: How to free a malloc'd string?

2019-04-21 20:40发布

问题:

In a C dll, I have a function like this:

char* GetSomeText(char* szInputText)
{
      char* ptrReturnValue = (char*) malloc(strlen(szInputText) * 1000); // Actually done after parsemarkup with the proper length
      init_parser(); // Allocates an internal processing buffer for ParseMarkup result, which I need to copy
      sprintf(ptrReturnValue, "%s", ParseMarkup(szInputText) );
      terminate_parser(); // Frees the internal processing buffer
      return ptrReturnValue;
}

I would like to call it from C# using P/invoke.

[DllImport("MyDll.dll")]
private static extern string GetSomeText(string strInput);

How do I properly release the allocated memory?

I am writing cross-platform code targeting both Windows and Linux.

Edit: Like this

[DllImport("MyDll.dll")]
private static extern System.IntPtr GetSomeText(string strInput);

[DllImport("MyDll.dll")]
private static extern void FreePointer(System.IntPtr ptrInput);

IntPtr ptr = GetSomeText("SomeText");
string result = Marshal.PtrToStringAuto(ptr);
FreePointer(ptr);

回答1:

You should marshal returned strings as IntPtr otherwise the CLR may free the memory using the wrong allocator, potentially causing heap corruption and all sorts of problems.

See this almost (but not quite) duplicate question PInvoke for C function that returns char *.

Ideally your C dll should also expose a FreeText function for you to use when you wish to free the string. This ensures that the string is deallocated in the correct way (even if the C dll changes).



回答2:

Add another function ReturnSomeText that calls free or whatever is needed to release the memory again.



回答3:

If you return to .net memory allocated with your native malloc, then you also have to export the deallocator. I don't regard that to be a desirable action and instead prefer to export the text as a BSTR. This can be freed by the C# runtime because it knows that the BSTR was allocated by the COM allocator. The C# coding becomes a lot simpler.

The only wrinkle is that a BSTR uses Unicode characters and your C++ code uses ANSI. I would work around that like so:

C++

#include <comutil.h>
BSTR ANSItoBSTR(const char* input)
{
    BSTR result = NULL;
    int lenA = lstrlenA(input);
    int lenW = ::MultiByteToWideChar(CP_ACP, 0, input, lenA, NULL, 0);
    if (lenW > 0)
    {
        result = ::SysAllocStringLen(0, lenW);
        ::MultiByteToWideChar(CP_ACP, 0, input, lenA, result, lenW);
    } 
    return result;
}

BSTR GetSomeText(char* szInputText)
{
      return ANSItoBSTR(szInputText);
}

C#

[DllImport("MyDll.dll", CallingConvention=CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string GetSomeText(string strInput);