I just recently learned about SafeHandle
and, for a test, I implemented it for the SDL2 library, creating and destroying a window:
[DllImport(_libName, CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr SDL_CreateWindow(
[MarshalAs(UnmanagedType.LPStr)] string title, int x, int y, int w, int h, uint flags);
[DllImport(_libName, CallingConvention = CallingConvention.Cdecl)]
internal static extern void SDL_DestroyWindow(IntPtr window);
public class Window : SafeHandleZeroOrMinusOneIsInvalid
{
public Window() : base(true)
{
SetHandle(SDL_CreateWindow("Hello", 400, 400, 800, 600, 0));
}
protected override bool ReleaseHandle()
{
SDL_DestroyWindow(handle);
return true;
}
}
This works fine, then I learned of another advantage of using SafeHandle
: The possibility to use the class in the p/invoke signature directly, like so:
[DllImport(_libName, CallingConvention = CallingConvention.Cdecl)]
internal static extern Window SDL_CreateWindow(
[MarshalAs(UnmanagedType.LPStr)] string title, int x, int y, int w, int h, uint flags);
[DllImport(_libName, CallingConvention = CallingConvention.Cdecl)]
internal static extern void SDL_DestroyWindow(Window window);
This is of course much better than generic IntPtr
parameters / returns, because I have type safety passing / retrieving actual Window
(handles) to / from those methods.
While this works for SDL_CreateWindow
, which correctly returns a Window
instance now, it doesn't work for SDL_DestroyWindow
, which is called by me inside of Window.ReleaseHandle
like this:
public Window() : base(true)
{
SetHandle(SDL_CreateWindow("Hello", 400, 400, 800, 600, 0).handle);
}
protected override bool ReleaseHandle()
{
SDL_DestroyWindow(this);
return true;
}
When trying to pass this
to SDL_DestroyWindow
, I get an ObjectDisposedException
: Safe handle has been closed. Indeed the IsClosed
property is true
, which I didn't expect to be at this time. Apparently it internally tries to increase a reference count, but notices IsClosed
is true
. According to the documentation, it has been set to true
because "The Dispose method or Close method was called and there are no references to the SafeHandle object on other threads.", so I guess Dispose
was implicitly called before in the call stack to invoke my ReleaseHandle
.
ReleaseHandle
apparently isn't the correct place to clean up if I want to use a class parameter in the p/invoke signature, so I wonder if there is any method where I could clean up without breaking SafeHandle
internals?