14

我写了一个辅助方法,

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# 最终会清理它分配的任何内存吗?

如果没有,有没有办法可以将调用包装SDL.RenderCopy在一些using语句中,这样我就不必做所有这些临时变量+显式释放废话?

4

4 回答 4

28

是的,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使用此包装器,您可以通过将对象包装在语句中或允许在终结器运行时释放它来手动释放内存。

于 2013-07-10T04:21:39.810 回答
20

很多人不知道这一点(这就是为什么你得到这么多答案说你不知道的原因),但是 .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是您控制的类,但这些也可能被切换到SafeHandles 。


更新:如果您想了解有关如何正确处理非托管资源的更多信息(并获得IDisposable比 MSDN 给出的示例更好的实现模式的示例),我强烈推荐文章“ IDisposable:您的母亲从未说过的事情关于资源重新分配的问题”,Stephen Cleary着。他在文章中深入探讨了如何正确编写自己的 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;
        }
    }
}
于 2013-07-10T05:51:34.977 回答
1

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

于 2013-07-10T04:15:01.053 回答
0

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

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

我过去所做的是将我的AllocHGlobal分配包装在一个IDisposable包装类中,在该类中Dispose()调用FreeHGlobal指针。这样,我可以将它们放在一个using声明中。

于 2013-07-10T04:15:34.173 回答