Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If I allocate some memory with AllocHGlobal, do I have to free it with FreeHGlobal?

I wrote a helper method,

internal static IntPtr StructToPtr(object obj)
{
    var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(obj));
    Marshal.StructureToPtr(obj, ptr, false);
    return ptr;
}

Which takes a struct and gives me back an IntPtr to it. I use it as such:

public int Copy(Texture texture, Rect srcrect, Rect dstrect)
{
    return SDL.RenderCopy(_ptr, texture._ptr, Util.StructToPtr(srcrect), Util.StructToPtr(dstrect));
}

The problem is that I only need that IntPtr for a split second so that I can pass it off to the C DLL,

[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_RenderCopy")]
internal static extern int RenderCopy(IntPtr renderer, IntPtr texture, IntPtr srcrect, IntPtr dstrect);

I don't really want to have to worry about freeing it; otherwise my 1-line function grows to 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;
}

Is there a better way to do this? Will C# eventually clean up any memory it has allocated?

If not, is there a way I can wrap the call to SDL.RenderCopy in some using statements instead so that I don't have to do all this temporary variable + explicit freeing non-sense?

like image 323
mpen Avatar asked Jul 10 '13 04:07

mpen


2 Answers

Yes, C# will not automatically free memory allocated by Marshal.AllocHGlobal. That memory must be freed with a call to Marshal.FreeHGlobal or else it will be leaked.

You could create something a smart pointer to wrap the 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 this wrapper you can either manually free the memory by wrapping the object in a using statement or by allowing it to be freed when the finalizer runs.

like image 100
shf301 Avatar answered Oct 07 '22 22:10

shf301


A lot of people don't know this (and that's why you got so many answers saying you can't), but there is something built in to .NET just for things like that: SafeHandle.

In fact, the .NET 2.0 page for one of its derived classes has a example using AllocHGlobal. When the finalizer of the SafeUnmanagedMemoryHandle is called it will automatically call FreeHGlobal for you. (If you want deterministic cleanup instead of just waiting for the finalizer to get around to it you will need to either call Close() or Dispose() explicitly).

It just takes a few code changes on your part:

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

Once you do that your original Copy example will work exactly like you expected it to.

public int Copy(Texture texture, Rect srcrect, Rect dstrect)
{
    return SDL.RenderCopy(_ptr, texture._ptr, Util.StructToPtr(srcrect), Util.StructToPtr(dstrect));
}

When the two pointers go out of scope and are finalized they will be cleaned up afterwards. I don't know how _ptr is used or if Texture is a class you control but those could potentially be switched to SafeHandles too.


UPDATE: If you want to learn more about how to properly deal with unmanaged resources (and get a example of a better pattern of how to implement IDisposable better than the example the MSDN gives) I highly recommend the article "IDisposable: What Your Mother Never Told You About Resource Deallocation" by Stephen Cleary. He goes into depth on how to properly write your own SafeHandles in the article.


APPENDIX

Here is a copy of the example in case the link ever goes dead:

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;
        }
    }
}
like image 26
Scott Chamberlain Avatar answered Oct 07 '22 22:10

Scott Chamberlain