This works:
[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetError")]
private static extern IntPtr SDL_GetError();
public static string GetError()
{
return Marshal.PtrToStringAnsi(SDL_GetError());
}
This crashes:
[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetError")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string GetError();
This article suggests that the return attribute is essentially like calling Marshal.PtrToStringAnsi
, so what's the deal?
As Daniel pointed out, it's probably crashing because the marshaller is attempting to free the memory. The article also states,
N.B. : Note that the unmanaged side must not use the “new” keyword or the “malloc()” C function to allocate memory. The Interop Marshaler will not be able to free the memory in these situations. This is because the “new” keyword is compiler dependent and the “malloc” function is C-library dependent.
I've tried freeing the char pointer with Marshal.FreeHGlobal
, Marshal.FreeCoTaskMem
and Marshal.FreeBSTR
-- they all crash. There aren't any other ways of freeing the memory AFAIK, so I'm guessing the memory was allocated via new
or malloc()
. So what now, I'm hooped? I have a permanent memory leak in my program?
I checked the source. The string is created via static char errmsg[SDL_ERRBUFIZE]
. My C is rusty, but I guess it's declared as static
so that it doesn't get freed when it goes out of function scope. I don't remember where static arrays live in memory-land though; is there some way of freeing them?
Edit: Wait... it's static. That means each time there's a new error it will overwrite the old error message, hence why SDL_GetError()
only returns the most recent error message. Ergo, I don't have to worry about freeing it.
As such, if all the return: MarshalAs...
options attempt to free the memory, then the only solution is my current one. This is optimal after all.
As stated in the linked article, when using [return: MarshalAs(UnmanagedType.LPStr)]
, the memory of the native string is freed by the CLR using FreeCoTaskMem()
. If you manually create the managed string object via Marshal.PtrToStringAnsi()
, the memory is not freed at all.
If it crashes, then probably the string was not created on the unmanaged side via CoTaskMemAlloc()
, but via new() or malloc() (for example). The API of SDL_GetError()
should state whose job it is to free the native string and how.
I did some digging. The source for SDL_GetError
is:
const char *
SDL_GetError(void)
{
static char errmsg[SDL_ERRBUFIZE];
return SDL_GetErrorMsg(errmsg, SDL_ERRBUFIZE);
}
We can see that the memory for the string is allocated as a static char array. It is overwritten every time SDL_GetError
is called. As such we can't and don't need to free it.
Since the [return: MarshalAs.*]
methods all attempt to free memory after marshalling the type, they will not work (and further cause the program to crash).
As such, your (my) original solution is optimal.
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