C# 如果我用AllocHGlobal分配一些内存,我是否必须用FreeHGlobal释放它?

C# 如果我用AllocHGlobal分配一些内存,我是否必须用FreeHGlobal释放它?,c#,interop,C#,Interop,我写了一个助手方法 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, Rec

我写了一个助手方法

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
的调用封装在一些
中,这样我就不必执行所有这些临时变量+显式释放无意义的操作?

是的,您必须释放它,并且您的6行程序所采用的方法非常有效。这是您在走出垃圾收集器时所做的权衡

不幸的是,没有内置的自动方式来实现这一点。如果调用,则必须使用显式释放它(除非您对潜在的大规模内存泄漏没有意见)

必须使用此方法释放此内存

我过去所做的是将我的
AllocHGlobal
分配打包到
IDisposable
包装类中,其中
Dispose()
在指针上调用
FreeHGlobal
。这样,我可以使用
语句将它们放入一个
中。

是的,C#不会自动释放由
marshall.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;
    }
}

使用此包装器,您可以手动释放内存,方法是使用
语句将对象包装在
中,或者允许在终结器运行时释放它。

很多人不知道这一点(这就是为什么有这么多答案说您不能),但是.NET中内置了一些东西,用于类似的事情:

事实上,的.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);
一旦您这样做,您的原始
副本
示例将完全按照您的预期工作

public int Copy(Texture texture, Rect srcrect, Rect dstrect)
{
    return SDL.RenderCopy(_ptr, texture._ptr, Util.StructToPtr(srcrect), Util.StructToPtr(dstrect));
}
当两个指针超出范围并最终确定时,它们将在之后被清除。我不知道如何使用
\u ptr
,或者
纹理
是否是您控制的类,但这些可能也会切换到
安全句柄
s


更新:如果您想了解有关如何正确处理非托管资源的更多信息(并获得一个比MSDN给出的示例更好的实现
IDisposable
的更好模式的示例),我强烈推荐您的“”一文。他在文章中深入探讨了如何正确地编写自己的安全句柄


附录

下面是一个示例的副本,以防链接失效:

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;
        }
    }
}

+1唯一能让这更好的是
隐式的
IntPtr
的转换,这样它就可以被传递到他的API调用,而不需要
.Ptr
。我要做的另一个更改是允许构造函数取
null
并将
Ptr
初始化为
IntPtr.Zero
。我假设我们刚刚开发的这个结构是CC许可的?:-)这个答案真的只是重新发明,但是SafeHandle的好处是释放是一个受约束的执行区域,这样它就不会被诸如
Thread.Abort()
之类的东西打断,它也可以直接从P/Invoke调用返回。由于当前(2015/05/09)编写了ctor(
if(Ptr!=null)
)我认为这是错误的。ctor不应该检查
obj
以查看它是否为空,并相应地设置
Ptr
?Reading Mark在2013年7月10日4:40给出的建议。他当前代码的唯一问题是
SDL.RenderCopy
可能会引发异常(不确定,还没有检查文档),在这种情况下,他会泄漏内存,因为自由方法从未执行过。不要认为会,但如果确实发生了,这将是一个问题。所有SDL方法在失败时返回0。**cough****cough**+1。考虑到
AllocHGlobal
的通用性,为什么
SafeUnmanagedMemoryHandle
不属于BCL的一部分?不知道,他们还从MSDN中删除了2.0以后版本的示例。我看到
SafeHandle
用于对
CreateFile
的pinvoke调用,但我想知道为什么他们会放弃这样的东西。我甚至不知道这里有谁会知道。那太棒了。我将调整shf301的类以使用SafeHandle。MSDN上的例子有点奇怪;为什么对
if(handle!=IntPtr.Zero)
if
releasehold
的显式检查只调用“如果句柄按照IsInvalid属性的定义是有效的”()?