Safehandle in C #

Cos’è SafeHandle? in cosa differisce da IntPtr? Quando dovrei usarne uno? Quali sono i suoi vantaggi?

Penso che MSDN sia abbastanza chiaro nella definizione:

La class SafeHandle fornisce la finalizzazione critica delle risorse di gestione, impedendo che le maniglie vengano recuperate prematuramente dalla procedura di Garbage Collection e che vengano riciclate da Windows per fare riferimento a oggetti non gestiti non intenzionali. Prima di .NET Framework versione 2.0, tutti gli handle di sistema operativo potevano essere incapsulati solo nell’object wrapper gestito IntPtr.

La class SafeHandle contiene un finalizzatore che garantisce che l’handle sia chiuso ed è garantito che venga eseguito, anche durante gli AppDomain inaspettati, quando un host non si fida della coerenza dello stato di AppDomain.

Per ulteriori informazioni sui vantaggi dell’utilizzo di SafeHandle, vedere Safe Handles e Finalizzazione critica.

Questa class è astratta perché non è ansible creare un handle generico. Per implementare SafeHandle, devi creare una class derivata. Per creare classi derivate da SafeHandle, devi sapere come creare e liberare un handle del sistema operativo. Questo processo è diverso per diversi tipi di handle perché alcuni usano CloseHandle, mentre altri usano metodi più specifici come UnmapViewOfFile o FindClose. Per questo motivo, è necessario creare una class derivata di SafeHandle per ogni tipo di handle del sistema operativo; come MySafeRegistryHandle, MySafeFileHandle e MySpecialSafeFileHandle. Alcune di queste classi derivate sono pre-scritte e fornite nello spazio dei nomi Microsoft.Win32.SafeHandles.

Un altro modo di guardarlo: con SafeHandle, non dovresti quasi mai scrivere un altro finalizzatore.

Dovresti utilizzare una derivata di SafeHandle quando ansible dove il codice gestito sta ricevendo un IntPtr dal codice non gestito. Mentre il nome, l’uso generale e persino la documentazione della class SafeHandle implica che si suppone che sia utilizzato solo per contenere gli handle del sistema operativo Windows, alcune classi di framework .NET interne come Microsoft.Win32.SafeHandles.SafeLocalAllocHandle e quelle che derivano dalla class astratta pubblicamente disponibile System.Runtime.InteropServices.SafeBuffer lo usa anche per garantire che vengano liberate altre risorse non gestite come le strutture e gli array allocati dynamicmente. In generale, credo che sia una buona pratica creare una derivata di questa class ogni volta che un IntPtr viene restituito al codice gestito dal codice non gestito anche se non richiede la pulizia.

Lo scopo stabilito di SafeHandle è quello di garantire che anche se il mondo sta finendo (ad esempio un AppDomain viene scaricato o si verifica una StackOverflowException), il framework .NET dovrebbe accertarsi che il finalizzatore di SafeHandle sia chiamato a chiudere o deallocare il non gestito quadro a cui si riferisce l’IntPtr spostato. La class SafeHandle ottiene ciò ereditando dalla class CriticalFinalizerObject . L’ereditarietà da questa class, tuttavia, impone all’erede l’obbligo di non rovinare completamente lo stato del processo quando viene chiamato il finalizzatore, il che probabilmente è il motivo per cui non viene spesso utilizzato per entity framework diverse dagli handle del sistema operativo Windows. Il framework .NET fornisce anche alcuni deboli ordini di finalizzazione in modo che sia sicuro interagire con un object SafeHandle nel finalizzatore di qualsiasi class che non eredita da CriticalFinalizerObject, ma le circostanze in cui è necessario dovrebbero essere poche e distanti tra loro.

Idealmente, una class derivata da SafeHandle dovrebbe anche essere utilizzata per interagire in modo più sicuro con un riferimento di entity framework non gestito incapsulando la funzionalità prevista all’interno della class derivata. Una class ben scritta che eredita da SafeHandle dovrebbe avere uno scopo specifico in mente e dovrebbe fornire metodi che siano sufficienti a impedire a qualsiasi sviluppatore che lo utilizza a tale scopo di dover sempre interagire direttamente con l’IntPtr che contiene. L’aggiunta di tali metodi fornisce anche ad altri sviluppatori una chiara idea di quale sia il risultato di una chiamata di metodo non gestita da utilizzare in un contesto gestito. Una class che eredita da SafeHandle può essere utilizzata per questo anche se non è richiesta alcuna pulizia sul puntatore che il metodo non gestito restituisce chiamando base (false) nel costruttore per la class.

Di seguito sono riportati due esempi che utilizzano classi derivate da SafeHandle per pulire in modo sicuro un riferimento a un’ quadro non gestita e incapsulare la funzionalità relativa all’entity framework non gestita. Il primo esempio è uno scenario più tradizionale in cui un token utente restituito da LogonUser è avvolto da un’istanza della class SafeTokenHandle. Questa class chiamerà CloseHandle sul token quando l’object viene eliminato o finalizzato. Include anche un metodo chiamato GetWindowsIdentity che restituisce un object WindowsIdentity per l’utente rappresentato dal token dell’utente. Il secondo esempio utilizza la funzione incorporata di Windows CommandLineToArgvW per analizzare una riga di comando. Questa funzione restituisce un puntatore a una matrice contenuta in un blocco contiguo di memoria che può essere liberato da una singola chiamata a LocalFree. La class SafeLocalAllocWStrArray (che eredita dalla class SafeLocalAllocArray che è anche definita in questo esempio) chiamerà LocalFree sull’array quando l’object viene eliminato o finalizzato. Include anche una funzione che copierà il contenuto dell’array non gestito in un array gestito.

static class Examples { static void Example1_SafeUserToken() { const string user = "SomeLocalUser"; const string domain = null; const string password = "ExamplePassword"; NativeMethods.SafeTokenHandle userToken; WindowsIdentity identity; NativeMethods.LogonUser(user, domain, password, NativeMethods.LogonType.LOGON32_LOGON_INTERACTIVE, NativeMethods.LogonProvider.LOGON32_PROVIDER_DEFAULT, out userToken); using (userToken) { // get a WindowsIdentity object for the user // WindowsIdentity will duplicate the token, so it is safe to free the original token after this is called identity = userToken.GetWindowsIdentity(); } // impersonate the user using (identity) using (WindowsImpersonationContext impersonationContext = identity.Impersonate()) { Console.WriteLine("I'm running as {0}!", Thread.CurrentPrincipal.Identity.Name); } } static void Example2_SafeLocalAllocWStrArray() { const string commandLine = "/example /command"; int argc; string[] args; using (NativeMethods.SafeLocalAllocWStrArray argv = NativeMethods.CommandLineToArgvW(commandLine, out argc)) { // CommandLineToArgvW returns NULL on failure; since SafeLocalAllocWStrArray inherits from // SafeHandleZeroOrMinusOneIsInvalid, it will see this value as invalid // if that happens, throw an exception containing the last Win32 error that occurred if (argv.IsInvalid) { int lastError = Marshal.GetHRForLastWin32Error(); throw new Win32Exception(lastError, "An error occurred when calling CommandLineToArgvW."); } // the one unsafe aspect of this is that the developer calling this function must be trusted to // pass in an array of length argc or specify the length of the copy as the value of argc // if the developer does not do this, the array may end up containing some garbage or an // AccessViolationException could be thrown args = new string[argc]; argv.CopyTo(args); } for (int i = 0; i < args.Length; ++i) { Console.WriteLine("Argument {0}: {1}", i, args[i]); } } } ///  /// P/Invoke methods and helper classs used by this example. ///  internal static class NativeMethods { // documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, LogonType dwLogonType, LogonProvider dwLogonProvider, out SafeTokenHandle phToken); // documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx [DllImport("kernel32.dll", SetLastError = true)] public static extern bool CloseHandle(IntPtr handle); // documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern SafeLocalAllocWStrArray CommandLineToArgvW(string lpCmdLine, out int pNumArgs); // documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366730(v=vs.85).aspx [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr LocalFree(IntPtr hLocal); ///  /// Wraps a handle to a user token. ///  public class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid { ///  /// Creates a new SafeTokenHandle. This constructor should only be called by P/Invoke. ///  private SafeTokenHandle() : base(true) { } ///  /// Creates a new SafeTokenHandle to wrap the specified user token. ///  /// The user token to wrap. /// true to close the token when this object is disposed or finalized, /// false otherwise. public SafeTokenHandle(IntPtr handle, bool ownHandle) : base(ownHandle) { this.SetHandle(handle); } ///  /// Provides a  object created from this user token. Depending /// on the type of token, this can be used to impersonate the user. The WindowsIdentity /// class will duplicate the token, so it is safe to use the WindowsIdentity object created by /// this method after disposing this object. ///  /// a  for the user that this token represents. /// This object does not contain a valid handle. /// This object has been disposed and its token has /// been released. public WindowsIdentity GetWindowsIdentity() { if (this.IsClosed) { throw new ObjectDisposedException("The user token has been released."); } if (this.IsInvalid) { throw new InvalidOperationException("The user token is invalid."); } return new WindowsIdentity(this.handle); } ///  /// Calls  to release this user token. ///  /// true if the function succeeds, false otherwise. To get extended /// error information, call . protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(this.handle); } } ///  /// A wrapper around a pointer to an array of Unicode strings (LPWSTR*) using a contiguous block of /// memory that can be freed by a single call to LocalFree. ///  public sealed class SafeLocalAllocWStrArray : SafeLocalAllocArray { ///  /// Creates a new SafeLocalAllocWStrArray. This constructor should only be called by P/Invoke. ///  private SafeLocalAllocWStrArray() : base(true) { } ///  /// Creates a new SafeLocalallocWStrArray to wrap the specified array. ///  /// The pointer to the unmanaged array to wrap. /// true to release the array when this object /// is disposed or finalized, false otherwise. public SafeLocalAllocWStrArray(IntPtr handle, bool ownHandle) : base(ownHandle) { this.SetHandle(handle); } ///  /// Returns the Unicode string referred to by an unmanaged pointer in the wrapped array. ///  /// The index of the value to retrieve. /// the value at the position specified by  as a string. protected override string GetArrayValue(int index) { return Marshal.PtrToStringUni(Marshal.ReadIntPtr(this.handle + IntPtr.Size * index)); } } // This class is similar to the built-in SafeBuffer class. Major differences are: // 1. This class is less safe because it does not implicitly know the length of the array it wraps. // 2. The array is read-only. // 3. The type parameter is not limited to value types. ///  /// Wraps a pointer to an unmanaged array of objects that can be freed by calling LocalFree. ///  /// The type of the objects in the array. public abstract class SafeLocalAllocArray : SafeHandleZeroOrMinusOneIsInvalid { ///  /// Creates a new SafeLocalArray which specifies that the array should be freed when this /// object is disposed or finalized. /// true to reliably release the handle during the finalization phase; /// false to prevent reliable release (not recommended). ///  protected SafeLocalAllocArray(bool ownsHandle) : base(ownsHandle) { } ///  /// Converts the unmanaged object referred to by  to a managed object /// of type T. ///  /// The index of the value to retrieve. /// the value at the position specified by  as a managed object of /// type T. protected abstract T GetArrayValue(int index); // ///  /// Frees the wrapped array by calling LocalFree. ///  /// true if the call to LocalFree succeeds, false if the call fails. protected override bool ReleaseHandle() { return (NativeMethods.LocalFree(this.handle) == IntPtr.Zero); } ///  /// Copies the unmanaged array to the specified managed array. /// /// It is important that the length of  be less than or equal to the length of /// the unmanaged array wrapped by this object. If it is not, at best garbage will be read and at worst /// an exception of type  will be thrown. ///  /// The managed array to copy the unmanaged values to. /// The unmanaged array wrapped by this object has been /// freed. /// The pointer to the unmanaged array wrapped by this object /// is invalid. ///  is null. public void CopyTo(T[] array) { if (array == null) { throw new ArgumentNullException("array"); } this.CopyTo(array, 0, array.Length); } ///  /// Copies the unmanaged array to the specified managed array. /// /// It is important that  be less than or equal to the length of /// the array wrapped by this object. If it is not, at best garbage will be read and at worst /// an exception of type  will be thrown. ///  /// The managed array to copy the unmanaged values to. /// The index to start at when copying to . /// The number of items to copy to  /// The unmanaged array wrapped by this object has been /// freed. /// The pointer to the unmanaged array wrapped by this object /// is invalid. ///  is null. ///  is less than zero.-or- ///  is greater than the length of .-or- ///  is less than zero. /// The sum of  and  /// is greater than the length of . public void CopyTo(T[] array, int index, int length) { if (this.IsClosed) { throw new ObjectDisposedException(this.ToString()); } if (this.IsInvalid) { throw new InvalidOperationException("This object's buffer is invalid."); } if (array == null) { throw new ArgumentNullException("array"); } if (index < 0 || array.Length < index) { throw new ArgumentOutOfRangeException("index", "index must be a nonnegative integer that is less than array's length."); } if (length < 0) { throw new ArgumentOutOfRangeException("length", "length must be a nonnegative integer."); } if (array.Length < index + length) { throw new ArgumentException("length", "length is greater than the number of elements from index to the end of array."); } for (int i = 0; i < length; ++i) { array[index + i] = this.GetArrayValue(i); } } } ///  /// The type of logon operation to perform. ///  internal enum LogonType : uint { LOGON32_LOGON_BATCH = 1, LOGON32_LOGON_INTERACTIVE = 2, LOGON32_LOGON_NETWORK = 3, LOGON32_LOGON_NETWORK_CLEARTEXT = 4, LOGON32_LOGON_NEW_CREDENTIALS = 5, LOGON32_LOGON_SERVICE = 6, LOGON32_LOGON_UNLOCK = 7 } ///  /// The logon provider to use. ///  internal enum LogonProvider : uint { LOGON32_PROVIDER_DEFAULT = 0, LOGON32_PROVIDER_WINNT50 = 1, LOGON32_PROVIDER_WINNT40 = 2 } }