2013-07-10 11 views
7

Ho scritto un metodo di supporto,Se alloco memoria con AllocHGlobal, devo liberarlo con FreeHGlobal?

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

che prende un struct e mi restituisce un IntPtr ad esso. Io lo uso come tale:

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

Il problema è che ho solo bisogno che IntPtr per una frazione di secondo in modo che possa farlo passare alla DLL C,

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

io in realtà non voglio preoccuparti di liberarlo; altrimenti la mia funzione a 1 riga diventa 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'è un modo migliore per farlo? C# alla fine pulirà tutta la memoria che ha allocato?

In caso contrario, c'è un modo posso avvolgere la chiamata a SDL.RenderCopy in alcune dichiarazioni using invece in modo che io non devo fare tutto questo variabile temporanea + esplicito liberando non-senso?

risposta

20

Sì, C# non libererà automaticamente la memoria allocata da Marshal.AllocHGlobal. Quella memoria deve essere liberata con una chiamata a Marshal.FreeHGlobal oppure verrà fatta trapelare.

Si potrebbe creare qualcosa di un puntatore intelligente per avvolgere il 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; 
    } 
} 

Utilizzando questo wrapper è possibile liberare manualmente la memoria avvolgendo l'oggetto in una dichiarazione using o consentendole di essere liberato quando il finalizzatore piste.

+6

+1 L'unica cosa che farebbe meglio questo è una conversione 'implicita' in' IntPtr' in modo che potesse essere passata alla sua chiamata API senza la necessità di '.Ptr'. –

+0

+1 anche su questo, e anche +1 sulla conversione implicita, –

+0

, ma se dovessi farlo solo con questa funzione, non lo presenterei. Perché se seguo ciò che stai facendo, lo vuoi il più scattante possibile –

1

Sì, devi liberarlo e il modo in cui hai ottenuto il tuo programma di 6 linee è piuttosto efficiente. È il compromesso che fai quando esci dal garbage collector.

+1

L'unico problema con il suo codice attuale è che 'SDL.RenderCopy' potrebbe gettare un'eccezione (non so per certo, non ho controllato la documentazione), nel qual caso avrebbe perso memoria perché i metodi gratuiti non vengono mai eseguiti. –

+1

non penso che lo faccia, ma se lo facesse, sarebbe un problema. –

+0

Tutti i metodi SDL restituiscono 0 in caso di errore. – mpen

0

Sfortunatamente non esiste un modo automatico incorporato per farlo. Se chiami AllocHGlobal, devi liberarlo esplicitamente con FreeHGlobal (a meno che tu non stia bene con perdite di memoria potenzialmente massicce).

Questa memoria deve essere rilasciata utilizzando il metodo Marshal.FreeHGlobal.

Quello che ho fatto in passato, è avvolgere le mie AllocHGlobal allocazioni in una classe wrapper di IDisposable, dove Dispose() chiamate FreeHGlobal sul puntatore. In questo modo, I potrebbe inserirli in un'istruzione using.

+2

\ ** cough * \ * [sì c'è un modo automatico] (http://stackoverflow.com/a/17563315/80274) \ ** cough * \ * –

14

Un sacco di persone non lo sanno (e questo è il motivo per cui ci sono così tante risposte che dicono che non si può), ma c'è qualcosa di integrato in .NET solo per cose del genere: SafeHandle.

In realtà, la pagina .NET 2.0 per one of its derived classes ha un esempio che utilizza AllocHGlobal. Quando viene chiamato il finalizzatore di SafeUnmanagedMemoryHandle, chiamerà automaticamente FreeHGlobal.(Se si desidera una pulizia deterministica invece di aspettare che il finalizzatore passi in giro, sarà necessario chiamare lo Close() o Dispose() esplicitamente).

Si prende solo un paio di modifiche al codice da parte vostra:

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

Una volta che fate che il vostro Copy esempio originale funzionerà esattamente come ti aspettavi che.

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

Quando i due puntatori escono dall'ambito e sono finalizzati, verranno ripuliti in seguito. Non so come si usa _ptr o se Texture è una classe che controlli ma che potrebbero essere commutati anche a SafeHandle s.


UPDATE: Se volete saperne di più su come trattare correttamente con risorse non gestite (e ottenere un esempio di una migliore modello di come implementare IDisposable meglio che l'esempio MSDN dà) mi raccomando l'articolo "IDisposable: What Your Mother Never Told You About Resource Deallocation" di Stephen Cleary. Si approfondisce su come scrivere correttamente i propri SafeHandles nell'articolo.


APPENDICE

Ecco una copia della esempio nel caso in cui il legame mai si esaurisce:

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

+1 Fantastico. Perché 'SafeUnmanagedMemoryHandle' non dovrebbe far parte del BCL, considerando l'uso comune di' AllocHGlobal'? –

+0

Non so, hanno anche rimosso gli esempi dal MSDN per le versioni successive al 2.0 –

+0

Interessante. Vedo 'SafeHandle' usato per cose come le chiamate PInvoked a' CreateFile', ma mi chiedo perché si libererebbero di qualcosa del genere. Non sono nemmeno sicuro di chi qui lo saprebbe. –

Problemi correlati