9

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);
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442

3 Answers3

7

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).

Community
  • 1
  • 1
Justin
  • 84,773
  • 49
  • 224
  • 367
  • I was afraid something like this was the proper answer. I'm going to marshal the IntPtr to a string, and then pinvoke free in libc.so/msvcr80.dll with this IntPtr, and afterwards, set the IntPtr to IntPtr.Zero. Hmm, you're right, writing a FreeText function and pinvoke that might be less painful. Else I have to write a cross-platform wrapper around malloc (switch between libc.so and msvcr80.dll depending on OS, which would bring me right into the DLL version hell for msvcr80.dll). – Stefan Steiger Sep 07 '11 at 01:07
  • 1
    You are quite right that you should export your own `FreeMemory` function. Don't try to link directly to the C runtime since you may not get the right one. Exporting a deallocator guarantees that you get the same runtime as was used to allocate the memory. – David Heffernan Sep 07 '11 at 08:36
1

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

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
1

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);
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I'm pinvoking the shared object on Windows as well as on Linux. Your code is fine, but it uses the WinAPI, which is fine on Windows, but not on Linux. Besides, the compiler automatically casts the charset between ASCII and Unicode. And as long as the dll is not unicode-capable, there's no point in bstr anyway. – Stefan Steiger Sep 07 '11 at 01:04
  • You really ought to tell us all the information up front. Now you tell us that you are using Linux! Clearly that changes things. On Windows char* is ANSI rather than ASCII in fact. – David Heffernan Sep 07 '11 at 06:17
  • Sorry for that. I use Windows AND Linux. No, ANSI is true for Windows applications only. A console application (formerly DOS) is ASCII. – Stefan Steiger Sep 07 '11 at 06:35
  • Console apps in .net can't communicate with DOS. In fact there is no DOS any more. Windows console apps are proper windows apps and char* is ANSI there. On Linux char* is probably utf8. Anyway I understand your question now. Next time, mention Mono! – David Heffernan Sep 07 '11 at 06:46
  • char* is indeed nothing more than a binary 8-bit buffer. ANSI, ASCII and Unicode are the ways text output routines interpret these buffers. – Петър Петров Apr 19 '16 at 15:18