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?
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.
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 SafeHandle
s 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;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With