Safehandle in C #

What is SafeHandle? How is it different from IntPtr? When should I use it? What are its advantages?

+6
c #
source share
3 answers

I think MSDN is pretty clear in the definition:

The SafeHandle class provides critical completion of processing descriptor resources; prevention is prematurely crushed by garbage collection and recycling Windows refers to unintended unmanaged objects. Before the .NET Framework version 2.0, all valid system descriptors can be encapsulated in an IntPtr-managed wrapper object.

The SafeHandle class contains a finalizer that ensures that the handle is closed and guaranteed to work, even during an unexpected AppDomain unloads when the host may not trust the consistency of the AppDomain state.

For more information about the benefits of using SafeHandle, see Safe Handles and Critical Refinement.

This class is abstract because you cannot create a common descriptor. To implement SafeHandle, you must create a derived class. To create SafeHandle you must know how to create and release the operating system pen. This process is different for different types of handles, because some use CloseHandle, while others use more specific methods, such as UnmapViewOfFile or FindClose. For this reason, you must create a SafeHandle derived class for each operating system handle type; such as MySafeRegistryHandle, MySafeFileHandle and MySpecialSafeFileHandle. Any of these derived classes are pre-recorded and provided to you in the Microsoft.Win32.SafeHandles Namespace.

+8
source share

Another way to look at this: With SafeHandle, you hardly need to write another finalizer.

+8
source share

You should use the SafeHandle derivative whenever possible, where managed code gets IntPtr from unmanaged code. Although the name, general use, and even documentation of the SafeHandle class implies that it should only be used to store Windows operating system descriptors, several internal .NET Framework classes such as Microsoft.Win32.SafeHandles.SafeLocalAllocHandle and those made from the public abstract System class .Runtime.InteropServices.SafeBuffer also use it to guarantee the release of other unmanaged resources, such as dynamically allocated structures and arrays. In general, I believe that it is good practice to create a derivative of this class whenever IntPtr returns to managed code from unmanaged code, even if it does not require cleaning.

The established goal of SafeHandle is to ensure that even if the world ends (for example, an AppDomain is unloaded or a StackOverflowException event is raised), the .NET platform must be absolutely sure that the finalizer for SafeHandle is called to close or free the unmanaged object referenced by the wrapped IntPtr. The SafeHandle class achieves this by inheriting from the CriticalFinalizerObject class. However, inheriting from this class, however, imposes an obligation on the heir not to completely depend on the state of the process when the finalizer is called, which is most likely not often used for entities other than Windows operating system descriptors. The .NET platform also provides some weak finalization orders, so it’s safe to interact with the SafeHandle object in the finalizer of any class that does not inherit from CriticalFinalizerObject, but the circumstances in which this is necessary should be small and far from each other.

Ideally, the SafeHandle class should also be used to more securely interact with an unmanaged object reference, by encapsulating the expected functionality in a derived class. A well-written class that inherits from SafeHandle should have a specific purpose and should provide methods sufficient so that any developer using it for this purpose never needs to interact directly with the IntPtr contained in it. Adding such methods also provides other developers with a clear idea that the result of an unmanaged method call should be used in a controlled context. A class that inherits from SafeHandle can be used to do this, even if the pointer that the unmanaged method returns does not need to be cleared by calling base (false) in the constructor for the class.

The following are two examples that use classes that are manufactured by SafeHandle to safely clear a reference to an unmanaged object and encapsulate the functionality associated with an unmanaged object. The first example is a more conventional scenario in which the user token returned LogonUser wrapped SafeTokenHandle instance class. This class will call CloseHandle on the token when the object is deleted or completed. It also includes the GetWindowsIdentity method, which returns a WindowsIdentity object for the user represented by the user token. The second example uses the built-in Windows CommandLineToArgvW function to parse the command line. This function returns a pointer to an array containing a contiguous block of memory that can be freed with a single call to LocalFree. The SafeLocalAllocWStrArray class (which inherits from the SafeLocalAllocArray class, which is also defined in this example) will call LocalFree in the array when the object is deleted or finalized. It also includes a function that copies the contents of an unmanaged array into a managed array.

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]); } } } /// <summary> /// P/Invoke methods and helper classes used by this example. /// </summary> 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); /// <summary> /// Wraps a handle to a user token. /// </summary> public class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid { /// <summary> /// Creates a new SafeTokenHandle. This constructor should only be called by P/Invoke. /// </summary> private SafeTokenHandle() : base(true) { } /// <summary> /// Creates a new SafeTokenHandle to wrap the specified user token. /// </summary> /// <param name="arrayPointer">The user token to wrap.</param> /// <param name="ownHandle"><c>true</c> to close the token when this object is disposed or finalized, /// <c>false</c> otherwise.</param> public SafeTokenHandle(IntPtr handle, bool ownHandle) : base(ownHandle) { this.SetHandle(handle); } /// <summary> /// Provides a <see cref="WindowsIdentity" /> 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. /// </summary> /// <returns>a <see cref="WindowsIdentity" /> for the user that this token represents.</returns> /// <exception cref="InvalidOperationException">This object does not contain a valid handle.</exception> /// <exception cref="ObjectDisposedException">This object has been disposed and its token has /// been released.</exception> 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); } /// <summary> /// Calls <see cref="NativeMethods.CloseHandle" /> to release this user token. /// </summary> /// <returns><c>true</c> if the function succeeds, <c>false otherwise</c>. To get extended /// error information, call <see cref="Marshal.GetLastWin32Error"/>.</returns> protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(this.handle); } } /// <summary> /// 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. /// </summary> public sealed class SafeLocalAllocWStrArray : SafeLocalAllocArray<string> { /// <summary> /// Creates a new SafeLocalAllocWStrArray. This constructor should only be called by P/Invoke. /// </summary> private SafeLocalAllocWStrArray() : base(true) { } /// <summary> /// Creates a new SafeLocalallocWStrArray to wrap the specified array. /// </summary> /// <param name="handle">The pointer to the unmanaged array to wrap.</param> /// <param name="ownHandle"><c>true</c> to release the array when this object /// is disposed or finalized, <c>false</c> otherwise.</param> public SafeLocalAllocWStrArray(IntPtr handle, bool ownHandle) : base(ownHandle) { this.SetHandle(handle); } /// <summary> /// Returns the Unicode string referred to by an unmanaged pointer in the wrapped array. /// </summary> /// <param name="index">The index of the value to retrieve.</param> /// <returns>the value at the position specified by <paramref name="index" /> as a string.</returns> 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. /// <summary> /// Wraps a pointer to an unmanaged array of objects that can be freed by calling LocalFree. /// </summary> /// <typeparam name="T">The type of the objects in the array.</typeparam> public abstract class SafeLocalAllocArray<T> : SafeHandleZeroOrMinusOneIsInvalid { /// <summary> /// Creates a new SafeLocalArray which specifies that the array should be freed when this /// object is disposed or finalized. /// <param name="ownsHandle"><c>true</c> to reliably release the handle during the finalization phase; /// <c>false</c> to prevent reliable release (not recommended).</param> /// </summary> protected SafeLocalAllocArray(bool ownsHandle) : base(ownsHandle) { } /// <summary> /// Converts the unmanaged object referred to by <paramref name="valuePointer" /> to a managed object /// of type T. /// </summary> /// <param name="index">The index of the value to retrieve.</param> /// <returns>the value at the position specified by <paramref name="index" /> as a managed object of /// type T.</returns> protected abstract T GetArrayValue(int index); // /// <summary> /// Frees the wrapped array by calling LocalFree. /// </summary> /// <returns><c>true</c> if the call to LocalFree succeeds, <c>false</c> if the call fails.</returns> protected override bool ReleaseHandle() { return (NativeMethods.LocalFree(this.handle) == IntPtr.Zero); } /// <summary> /// Copies the unmanaged array to the specified managed array. /// /// It is important that the length of <paramref name="array"/> 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 <see cref="AccessViolationException" /> will be thrown. /// </summary> /// <param name="array">The managed array to copy the unmanaged values to.</param> /// <exception cref="ObjectDisposedException">The unmanaged array wrapped by this object has been /// freed.</exception> /// <exception cref="InvalidOperationException">The pointer to the unmanaged array wrapped by this object /// is invalid.</exception> /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception> public void CopyTo(T[] array) { if (array == null) { throw new ArgumentNullException("array"); } this.CopyTo(array, 0, array.Length); } /// <summary> /// Copies the unmanaged array to the specified managed array. /// /// It is important that <paramref name="length" /> 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 <see cref="AccessViolationException" /> will be thrown. /// </summary> /// <param name="array">The managed array to copy the unmanaged values to.</param> /// <param name="index">The index to start at when copying to <paramref name="array" />.</param> /// <param name="length">The number of items to copy to <paramref name="array" /></param> /// <exception cref="ObjectDisposedException">The unmanaged array wrapped by this object has been /// freed.</exception> /// <exception cref="InvalidOperationException">The pointer to the unmanaged array wrapped by this object /// is invalid.</exception> /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.-or- /// <paramref name="index" /> is greater than the length of <paramref name="array"/>.-or- /// <paramref name="length"/> is less than zero.</exception> /// <exception cref="ArgumentException">The sum of <paramref name="index" /> and <paramref name="length" /> /// is greater than the length of <paramref name="array" />.</exception> 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 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 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); } } } /// <summary> /// The type of logon operation to perform. /// </summary> 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 } /// <summary> /// The logon provider to use. /// </summary> internal enum LogonProvider : uint { LOGON32_PROVIDER_DEFAULT = 0, LOGON32_PROVIDER_WINNT50 = 1, LOGON32_PROVIDER_WINNT40 = 2 } } 
+2
source share

All Articles