【问题标题】:If I allocate some memory with AllocHGlobal, do I have to free it with FreeHGlobal?如果我用 AllocHGlobal 分配了一些内存,我必须用 FreeHGlobal 释放它吗?
【发布时间】:2013-07-10 04:07:45
【问题描述】:

我写了一个辅助方法,

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

我真的不想担心释放它;否则我的 1 行函数会增长到 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# 最终会清理它分配的任何内存吗?

如果没有,有没有办法我可以在一些 using 语句中包装对 SDL.RenderCopy 的调用,这样我就不必执行所有这些临时变量 + 显式释放无意义的操作?

【问题讨论】:

    标签: c# interop


    【解决方案1】:

    是的,C# 不会自动释放由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 语句中,或者允许在终结器运行时释放它。

    【讨论】:

    • +1 唯一能让这变得更好的是implicit 转换为IntPtr,因此它可以传递给他的API 调用而不需要.Ptr
    • 我要做的另一项更改是允许构造函数采用null并将Ptr初始化为IntPtr.Zero
    • 我假设我们刚刚开发的这个结构是 CC 许可的? :-)
    • 这个答案实际上只是在重新发明SafeHandle,但是 SafeHandle 的好处是让释放成为一个受约束的执行区域,因此它不会被Thread.Abort() 之类的东西打断,它也可以直接从 P/Invoke 调用中返回。
    • 正如 ctor 目前 (2015/05/09) 写的 (if (Ptr != null)) 我认为这是错误的。 ctor 不应该检查 obj 以查看它是否为空并相应地设置 Ptr 吗?从 2013 年 7 月 10 日 4:40 开始阅读 Mark 的建议。
    【解决方案2】:

    很多人不知道这一点(这就是为什么你得到这么多回答说你不知道的原因),但是 .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;
            }
        }
    }
    

    【讨论】:

    • +1 太棒了。考虑到AllocHGlobal 的普遍使用,为什么SafeUnmanagedMemoryHandle 不成为BCL 的一部分?
    • 不知道,他们还从 MSDN 中删除了 2.0 之后版本的示例
    • 有趣。我看到SafeHandle 用于诸如对CreateFile 的PInvoked 调用之类的事情,但我想知道他们为什么要摆脱这样的事情。我什至不确定这里会有谁知道。
    • 这太棒了。我将调整 shf301 的类以改用 SafeHandle。 MSDN 上的例子有点奇怪。为什么对if (handle != IntPtr.Zero) if ReleaseHandle 的显式检查仅称为“如果句柄由 IsInvalid 属性定义的有效”(src)?
    【解决方案3】:

    是的,你必须释放它,你得到它作为你的 6 行程序的方式非常有效。这是您在离开垃圾收集器时所做的权衡。

    【讨论】:

    • 他当前代码的唯一问题是SDL.RenderCopy 可能会抛出异常(不确定,没有检查文档),在这种情况下他会泄漏内存,因为免费方法永远不会被处决。
    • 不认为有,但如果有,那就有问题了。
    • 所有 SDL 方法在失败时返回 0。
    【解决方案4】:

    不幸的是,没有内置的自动方法可以做到这一点。如果您调用AllocHGlobal,您必须使用FreeHGlobal 显式释放它(除非您可以接受潜在的大量内存泄漏)。

    此内存必须使用Marshal.FreeHGlobal 方法释放。

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-20
    • 1970-01-01
    • 2019-08-20
    • 1970-01-01
    • 1970-01-01
    • 2012-01-02
    • 1970-01-01
    相关资源
    最近更新 更多