C#中的安全处理

什么是SafeHandle? 它与IntPtr有何不同? 我应该什么时候使用? 它的优点是什么?

我认为MSDN的定义非常明确:

SafeHandle类提供了句柄资源的关键完成,防止句柄被垃圾收集过早地回收,并被Windows回收以引用非预期的非托管对象。 在.NET Framework 2.0版之前,所有操作系统句柄只能封装在IntPtr托管包装器对象中。

SafeHandle类包含一个终结器,可确保句柄关闭并保证运行,即使在主机可能不信任AppDomain状态的一致性时意外的AppDomain卸载期间也是如此。

有关使用SafeHandle的好处的更多信息,请参阅安全句柄和关键终结。

此类是抽象的,因为您无法创建通用句柄。 要实现SafeHandle,您必须创建派生类。 要创建SafeHandle派生类,您必须知道如何创建和释放操作系统句柄。 对于不同的句柄类型,此过程是不同的,因为一些使用CloseHandle,而其他使用更具体的方法,如UnmapViewOfFile或FindClose。 因此,您必须为每个操作系统句柄类型创建一个SafeHandle派生类; 例如MySafeRegistryHandle,MySafeFileHandle和MySpecialSafeFileHandle。 其中一些派生类是在Microsoft.Win32.SafeHandles命名空间中预先编写并提供的。

查看它的另一种方式:使用SafeHandle,您几乎不需要编写另一个终结器。

在托管代码从非托管代码接收IntPtr时,应尽可能使用SafeHandle的派生。 虽然SafeHandle类的名称,一般用途甚至文档都暗示它只应该用于包含Windows操作系统句柄,但是一些内部.NET框架类,如Microsoft.Win32.SafeHandles.SafeLocalAllocHandle和派生的那些从公开的抽象类System.Runtime.InteropServices.SafeBuffer也可以使用它来保证释放其他非托管资源,如动态分配的结构和数组。 一般来说,我认为,只要IntPtr从非托管代码返回到托管代码,即使它不需要清理,也可以创建此类的派生。

SafeHandle的既定目的是保证即使世界正在结束(例如正在卸载AppDomain或发生StackOverflowException),.NET框架应该绝对确保调用SafeHandle的终结器来关闭或取消分配非托管被包装的IntPtr引用的实体。 SafeHandle类通过inheritanceCriticalFinalizerObject类来实现此目的。 但是,inheritance这个类确实会使inheritance者有义务在调用终结器时不完全搞砸进程的状态,这可能是为什么它不常用于Windows操作系统句柄以外的实体。 .NET框架还提供了一些弱的终结顺序,因此可以安全地与任何不从CriticalFinalizerObjectinheritance的类的终结器中的SafeHandle对象进行交互,但是必要的情况应该很少。

理想情况下,还应使用SafeHandle派生类,通过在派生类中封装预期的function,更安全地与非托管实体引用进行交互。 从SafeHandleinheritance的编写良好的类应该有一个特定的目的,并且应该提供足以防止任何使用它的开发人员需要直接与它包含的IntPtr交互的方法。 添加此类方法还可以使其他开发人员清楚地了解在托管上下文中将使用非托管方法调用的结果。 即使在非托管方法通过在类的构造函数中调用base(false)而返回的指针上不需要清理,也可以使用inheritance自SafeHandle的类。

下面是两个使用从SafeHandle派生的类来安全地清理对非托管实体的引用并封装与非托管实体相关的function的示例。 第一个例子是一种更传统的方案,其中通过返回的用户令牌的LogonUser由SafeTokenHandle类的实例缠绕。 在处理或完成对象时,此类将在令牌上调用CloseHandle。 它还包括一个名为GetWindowsIdentity的方法,该方法为用户令牌表示的用户返回WindowsIdentity对象。 第二个示例使用Windows内置函数CommandLineToArgvW来解析命令行。 此函数返回一个指向包含连续内存块的数组的指针,该内存块可以通过对LocalFree的单次调用释放。 SafeLocalAllocWStrArray类(inheritance自SafeLocalAllocArray类(在此示例中也定义))将在处置或完成对象时调用arrays上的LocalFree。 它还包括一个将非托管数组的内容复制到托管数组的函数。

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 classes 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 } }