如果我用AllocHGlobal分配一些内存,我必须用FreeHGlobal释放它吗
本文关键字:FreeHGlobal 释放 内存 分配 AllocHGlobal 如果 | 更新日期: 2023-09-27 18:21:49
我写了一个助手方法
internal static IntPtr StructToPtr(object obj)
{
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(obj));
Marshal.StructureToPtr(obj, ptr, false);
return ptr;
}
它取一个struct
,然后给我一个IntPtr
。我这样使用它:
public int Copy(Texture texture, Rect srcrect, Rect dstrect)
{
return SDL.RenderCopy(_ptr, texture._ptr, Util.StructToPtr(srcrect), Util.StructToPtr(dstrect));
}
问题是,我只需要IntPtr
一瞬间,这样我就可以把它传递给C DLL,
[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_RenderCopy")]
internal static extern int RenderCopy(IntPtr renderer, IntPtr texture, IntPtr srcrect, IntPtr dstrect);
我真的不想担心释放它;否则,我的单行函数将增长到6:
public int Copy(Texture texture, Rect? srcrect=null, Rect? dstrect=null)
{
var srcptr = Util.StructToPtr(srcrect);
var dstptr = Util.StructToPtr(dstrect);
var result = SDL.RenderCopy(_ptr, texture._ptr, srcptr, dstptr);
Marshal.FreeHGlobal(srcptr);
Marshal.FreeHGlobal(dstptr);
return result;
}
有更好的方法吗?C#最终会清理它分配的内存吗?
如果没有,有没有一种方法可以将对SDL.RenderCopy
的调用封装在一些using
语句中,这样我就不必执行所有这些临时变量+显式释放无意义?
Marshal.AllocHGlobal
分配的内存。必须通过调用Marshal.FreeHGlobal
来释放该内存,否则它将被泄露。
你可以创建一个智能指针来包装IntPtr
class StructWrapper : IDisposable {
public IntPtr Ptr { get; private set; }
public StructWrapper(object obj) {
if (Ptr != null) {
Ptr = Marshal.AllocHGlobal(Marshal.SizeOf(obj));
Marshal.StructureToPtr(obj, Ptr, false);
}
else {
Ptr = IntPtr.Zero;
}
}
~StructWrapper() {
if (Ptr != IntPtr.Zero) {
Marshal.FreeHGlobal(Ptr);
Ptr = IntPtr.Zero;
}
}
public void Dispose() {
Marshal.FreeHGlobal(Ptr);
Ptr = IntPtr.Zero;
GC.SuppressFinalize(this);
}
public static implicit operator IntPtr(StructWrapper w) {
return w.Ptr;
}
}
使用这个包装器,您可以通过在using
语句中包装对象来手动释放内存,也可以在终结器运行时允许释放内存。
很多人都不知道这一点(这就是为什么你得到了这么多答案,说你不能),但.NET中内置了一些东西:SafeHandle。
事实上,它的一个派生类的.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
是否是您控制的类,但这些类也可能切换到SafeHandle
。
更新:如果您想了解更多关于如何正确处理非托管资源的信息(并获得一个如何更好地实现IDisposable
的模式的示例,而不是MSDN给出的示例),我强烈推荐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;
}
}
}
是的,你必须释放它,而且你把它作为6行程序的方式非常有效。这是您在走出垃圾收集器时所做的权衡。
不幸的是,没有内置的自动方法。如果调用AllocHGlobal
,则必须用FreeHGlobal
显式释放它(除非您可以处理潜在的大规模内存泄漏)。
必须使用
Marshal.FreeHGlobal
方法释放此内存。
我过去所做的是将AllocHGlobal
分配封装在IDisposable
包装类中,其中Dispose()
在指针上调用FreeHGlobal
。这样,我就可以将它们放在using
语句中。