很多人不知道这一点(这就是为什么你得到这么多回答说你不知道的原因),但是 .NET 中内置了一些东西来处理类似的事情:SafeHandle。
事实上,one of its derived classes 的 .NET 2.0 页面有一个使用 AllocHGlobal 的示例。当SafeUnmanagedMemoryHandle 的终结器被调用时,它会自动为你调用FreeHGlobal。 (如果您想要确定性清理而不是仅仅等待终结器处理它,您将需要显式调用 Close() 或 Dispose())。
您只需更改一些代码:
internal static SafeUnmanagedMemoryHandle StructToPtr(object obj)
{
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(obj));
Marshal.StructureToPtr(obj, ptr, false);
return new SafeUnmanagedMemoryHandle(ptr, true);
}
[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_RenderCopy")]
internal static extern int RenderCopy(IntPtr renderer, IntPtr texture, SafeUnmanagedMemoryHandle srcrect, SafeUnmanagedMemoryHandle dstrect);
一旦您这样做了,您原来的 Copy 示例将完全按照您的预期工作。
public int Copy(Texture texture, Rect srcrect, Rect dstrect)
{
return SDL.RenderCopy(_ptr, texture._ptr, Util.StructToPtr(srcrect), Util.StructToPtr(dstrect));
}
当两个指针超出范围并最终确定后,它们将被清理。我不知道_ptr 是如何使用的,或者Texture 是否是您控制的类,但这些也可能切换到SafeHandles。
更新:如果您想了解更多关于如何正确处理非托管资源的信息(并获得一个比 MSDN 给出的示例更好的如何实现 IDisposable 的示例)我强烈推荐Stephen Cleary 的文章“IDisposable: What Your Mother Never Told You About Resource Deallocation”。他在文章中深入探讨了如何正确编写自己的 SafeHandles。
附录
这是示例的副本,以防链接失效:
using System;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace SafeHandleExamples
{
class Example
{
public static void Main()
{
IntPtr ptr = Marshal.AllocHGlobal(10);
Console.WriteLine("Ten bytes of unmanaged memory allocated.");
SafeUnmanagedMemoryHandle memHandle = new SafeUnmanagedMemoryHandle(ptr, true);
if (memHandle.IsInvalid)
{
Console.WriteLine("SafeUnmanagedMemoryHandle is invalid!.");
}
else
{
Console.WriteLine("SafeUnmanagedMemoryHandle class initialized to unmanaged memory.");
}
Console.ReadLine();
}
}
// Demand unmanaged code permission to use this class.
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
sealed class SafeUnmanagedMemoryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// Set ownsHandle to true for the default constructor.
internal SafeUnmanagedMemoryHandle() : base(true) { }
// Set the handle and set ownsHandle to true.
internal SafeUnmanagedMemoryHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
// Perform any specific actions to release the
// handle in the ReleaseHandle method.
// Often, you need to use Pinvoke to make
// a call into the Win32 API to release the
// handle. In this case, however, we can use
// the Marshal class to release the unmanaged
// memory.
override protected bool ReleaseHandle()
{
// "handle" is the internal
// value for the IntPtr handle.
// If the handle was set,
// free it. Return success.
if (handle != IntPtr.Zero)
{
// Free the handle.
Marshal.FreeHGlobal(handle);
// Set the handle to zero.
handle = IntPtr.Zero;
// Return success.
return true;
}
// Return false.
return false;
}
}
}