I'm failing to understand why the .NET Core marshalling is failing to work with the one marshakking approach but not another. In the example below Marshal.PtrToStringAnsi
can successfully marshal a string from the native dll but the same call with the [return: MarshalAs(UnmanagedType.LPStr)]
attribute cannot? Or is the marshalling from the IntPtr just getting lucky and could also access invalid memory depending on the OS memory management?
The C library code is (compiled as C not C++):
static const char str[] = "Hello World";
__declspec(dllexport) const char* getstr() {
return str;
}
C# test program:
using System;
using System.Runtime.InteropServices;
namespace CLibraryPinvoke
{
class Program
{
public const string LIB_NAME = "CLibrary";
[DllImport(LIB_NAME, EntryPoint = "getstr")]
public static extern IntPtr getstrgood();
[DllImport(LIB_NAME, EntryPoint ="getstr")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string getstrbad();
static void Main(string[] args)
{
Console.WriteLine($"getstr={Marshal.PtrToStringAnsi(getstrgood())}.");
// Crash.
Console.WriteLine($"getstr={getstrbad()}.");
}
}
}
Execution result:
getstr=Hello World.
CLibraryPinvoke\bin\Debug\netcoreapp3.1\CLibraryPinvoke.exe (process 31848) exited with code -1073740940.
PtrToStringAnsi
copies the string from the buffer pointed to by the return value.
UnmanagedType.LPStr
copies the string from the buffer pointed to by the return value, and releases it with CoTaskMemFree
. Releasing a static const char[]
with CoTaskMemFree
crashes the program.
This is because the semantic of the return value is that the memory that holds the result was allocated on the called side.
As documented, if the memory is allocated on the called side, but should not be released with CoTaskMemFree
, you must marshal it as IntPtr
and release it yourself using the appropriate method.
The appropriate method for memory backed up by a static const char[]
is to leave it alone.
This requires the user of your function to have knowledge of implementation details of your function, which is not a good thing. Of course, you can document the function as returning memory that should never be released, but then it would probably not be a very useful function, and if you were to change its behaviour in the future, you would be stuck with returning one of the possible static const char[]
s.
You can avoid all that by allocating the memory each time and letting the .NET runtime to manage the rest, or by making the function accept a char*
argument to copy the response into.