我刚刚了解了SafeHandle
,为了测试,我为SDL2库实现了它,创建并销毁了一个窗口:
[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;
}
}
这很好用,然后我了解了使用SafeHandle
的另一个好处:可以直接在p / invoke签名中使用该类,如下所示:
[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);
这当然比通用IntPtr
参数/返回要好得多,因为我有类型安全传递/检索到这些方法的实际Window
(句柄)。
虽然这适用于现在正确返回SDL_CreateWindow
个实例的Window
,但它不适用于SDL_DestroyWindow
Window.ReleaseHandle
,我在public Window() : base(true)
{
SetHandle(SDL_CreateWindow("Hello", 400, 400, 800, 600, 0).handle);
}
protected override bool ReleaseHandle()
{
SDL_DestroyWindow(this);
return true;
}
内部调用这样:
this
尝试将SDL_DestroyWindow
传递给ObjectDisposedException
时,我得到IsClosed
:安全句柄已关闭。实际上true
属性是IsClosed
,我此时并不期望这样做。显然,它会在内部尝试增加引用计数,但通知true
为true
。根据{{3}},它被设置为Dispose
,因为“调用了Dispose方法或Close方法,并且在其他线程上没有对SafeHandle对象的引用。”所以我猜{{1}之前在调用堆栈中隐式调用,以调用我的ReleaseHandle
。
ReleaseHandle
显然不是正确的清理位置,所以我想知道是否有任何方法可以清除而不会破坏{{ 1}}内部结构?
答案 0 :(得分:0)
我上面的问题稍微被我从SafeHandle
学到的错误信息误导了(通过一些我不会提及的博客文章)。虽然我被告知在P / Invoke方法中用类实例替换IntPtr
参数是“ SafeHandle
提供的主要优势”并且非常好,但事实证明只有部分有用:
SafeHandle
创建首先,我这样说是因为我上面的代码有一个我一开始没看到的大问题。我写了这段代码:
void DoStuff()
{
Window window = new Window();
}
public class Window : SafeHandleZeroOrMinusOneIsInvalid
{
public Window() : base(true)
{
// SDL_CreateWindow will create another `Window` instance internally!!
SetHandle(SDL_CreateWindow("Hello", 400, 400, 800, 600, 0).handle);
}
protected override bool ReleaseHandle()
{
SDL_DestroyWindow(handle); // Since "this" won't work here (s. below)
return true;
}
// Returns Window instance rather than IntPtr via the automatic SafeHandle creation
[DllImport(_libName, CallingConvention = CallingConvention.Cdecl)]
private static extern Window SDL_CreateWindow(
[MarshalAs(UnmanagedType.LPStr)] string title, int x, int y, int w, int h, uint flags);
// Accept Window instance rather than IntPtr (won't work out, s. below)
[DllImport(_libName, CallingConvention = CallingConvention.Cdecl)]
private static extern void SDL_DestroyWindow(Window window);
}
当编组程序在SDL_CreateWindow
构造函数中调用Window
的P / Invoke方法时,它会在内部为返回的Window
类创建另一个实例value(调用必需的无参数构造函数,然后在内部设置handle
成员)。这意味着我现在有两个SafeHandle实例:
SDL_CreateWindow
方法返回的方法 - 我在任何地方都没有使用(仅剥离handle
属性)new Window()
,SafeHandle
类本身此处实现SafeHandle
的唯一正确方法是让SDL_CreateWindow
再次返回IntPtr
,因此不再创建内部编组SafeHandle
实例。
SafeHandle
ReleaseHandle
正如Simon Mourier在评论中解释/引用的那样,在SafeHandle
清理时,ReleaseHandle
本身就不能再使用了,因为对象是垃圾收集并试图做“奇特”的事情比如将它传递给P / Invoke方法不再安全/注定要失败。 (鉴于我被告知在P / Invoke中替换IntPtr
参数是SafeHandle
的“主要特征”之一,我首先感到惊讶的是,这不支持并被视为“花哨”) 。这也是我收到的ObjectDisposedException
非常合理的原因。
我仍然可以在此处访问handle
属性,但是,我的P / Invoke方法再次不再接受Window
实例,而是“经典”IntPtr
。
我会这样说,我的最终实现看起来像这样并且在仍然使用SafeHandle
的优点的同时解决了上述两个问题,只是没有花哨的P / Invoke参数替换。作为一个额外的功能,我仍然可以使用using
别名来暗示IntPtr参数“接受”SDL_Window(指向的本机类型)。
using SDL_Window = System.IntPtr;
public class Window : SafeHandleZeroOrMinusOneIsInvalid
{
private Window(IntPtr handle) : base(true)
{
SetHandle(handle);
}
public Window() : this(SDL_CreateWindow("Hello", 400, 400, 800, 600, 0)) { }
protected override bool ReleaseHandle()
{
SDL_DestroyWindow(handle);
return true;
}
[DllImport(_libName, CallingConvention = CallingConvention.Cdecl)]
private static extern SDL_Window SDL_CreateWindow(
[MarshalAs(UnmanagedType.LPStr)] string title, int x, int y, int w, int h, uint flags);
[DllImport(_libName, CallingConvention = CallingConvention.Cdecl)]
private static extern void SDL_DestroyWindow(SDL_Window window);
}