什么是SafeHandle?它与IntPtr有何不同?我应该什么时候使用?它的优点是什么?
另一种观察方式:使用SafeHandle,您几乎不需要编写另一个终结器.
我认为MSDN的定义非常明确:
SafeHandle类提供了句柄资源的关键最终化,防止句柄被垃圾收集过早地回收,并被Windows回收以引用非预期的非托管对象.在.NET Framework 2.0版之前,所有操作系统句柄只能封装在IntPtr托管包装器对象中.
SafeHandle类包含一个终结器,可确保句柄关闭并保证运行,即使在主机可能不信任AppDomain状态的一致性时意外的AppDomain卸载期间也是如此.
有关使用SafeHandle的好处的更多信息,请参阅安全句柄和关键终结.
此类是抽象的,因为您无法创建通用句柄.要实现SafeHandle,您必须创建派生类.要创建SafeHandle派生类,您必须知道如何创建和释放操作系统句柄.对于不同的句柄类型,此过程是不同的,因为一些使用CloseHandle,而其他使用更具体的方法,如UnmapViewOfFile或FindClose.因此,您必须为每个操作系统句柄类型创建一个SafeHandle派生类; 例如MySafeRegistryHandle,MySafeFileHandle和MySpecialSafeFileHandle.其中一些派生类是在Microsoft.Win32.SafeHandles命名空间中预先编写并提供的.
如果托管代码从非托管代码接收到IntPtr,则应尽可能使用SafeHandle的派生形式。尽管SafeHandle类的名称,一般用法甚至文档暗示它仅应用于包含Windows操作系统句柄,但一些内部.NET框架类(例如Microsoft.Win32.SafeHandles.SafeLocalAllocHandle)以及派生的类从公共可用的抽象类System.Runtime.InteropServices.SafeBuffer中也可以使用它来确保释放其他非托管资源,例如动态分配的结构和数组。通常,我认为,每当将IntPtr从非托管代码返回到托管代码时,即使不需要清除,也应创建此类的派生类,这是一个好习惯。
SafeHandle的既定目的是确保即使世界即将结束(例如,正在卸载AppDomain或发生StackOverflowException),. NET框架也应绝对确保调用SafeHandle的终结器来关闭或释放未托管的。包装的IntPtr引用该实体。SafeHandle类通过继承CriticalFinalizerObject实现此目的类。但是,从此类继承后,继承者确实承担了在调用终结器时不会完全搞乱进程状态的义务,这很可能就是为什么它不经常用于Windows操作系统句柄之外的实体的原因。.NET框架还提供了一些较弱的终结排序,因此可以安全地与任何不继承自CriticalFinalizerObject的类的终结器中的SafeHandle对象进行交互,但是在这种情况下,必要的情况应该很少。
理想情况下,还应该使用SafeHandle派生的类,通过将期望的功能封装在派生类中,以更安全地与非托管实体引用进行交互。从SafeHandle继承的编写良好的类应牢记一个特定的目的,并应提供足以防止任何为此目的使用它的开发人员直接与其所包含的IntPtr进行交互的方法。添加此类方法还为其他开发人员提供了一个清晰的概念,即在托管上下文中将使用非托管方法调用的结果。即使从非托管方法通过在类的构造函数中调用base(false)返回的指针不需要清除,也可以使用从SafeHandle继承的类进行此操作。
下面是两个示例,这些示例使用从SafeHandle派生的类安全地清理对非托管实体的引用并封装与非托管实体相关的功能。第一个例子是一种更传统的方案,其中通过令牌返回的用户的LogonUser由SafeTokenHandle类的实例缠绕。当对象被处置或完成时,此类将在令牌上调用CloseHandle。它还包括一个名为GetWindowsIdentity的方法,该方法为用户令牌代表的用户返回WindowsIdentity对象。第二个示例使用Windows内置函数CommandLineToArgvW解析命令行。该函数返回一个指向包含连续内存块的数组的指针,该内存块可以通过单次调用LocalFree来释放。当对象被处置或完成时,SafeLocalAllocWStrArray类(继承自本示例中定义的SafeLocalAllocArray类)将在数组上调用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. /// protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(this.handle); } } /// true if the function succeeds,false otherwise . To get extended /// error information, call. /// 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 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. ///as a string. /// 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. /// protected SafeLocalAllocArray(bool ownsHandle) : base(ownsHandle) { } ///true to reliably release the handle during the finalization phase; ///false to prevent reliable release (not recommended). ////// Converts the unmanaged object referred to by /// The index of the value to retrieve. ///to a managed object /// of type T. /// the value at the position specified by protected abstract T GetArrayValue(int index); // ///as a managed object of /// type T. /// Frees the wrapped array by calling LocalFree. /// ///protected override bool ReleaseHandle() { return (NativeMethods.LocalFree(this.handle) == IntPtr.Zero); } /// true if the call to LocalFree succeeds,false if the call fails./// Copies the unmanaged array to the specified managed array. /// /// It is important that the length of /// The managed array to copy the unmanaged values to. ///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 unmanaged array wrapped by this object has been /// freed. ///The pointer to the unmanaged array wrapped by this object /// is invalid. ///public void CopyTo(T[] array) { if (array == null) { throw new ArgumentNullException("array"); } this.CopyTo(array, 0, array.Length); } /// is null. /// Copies the unmanaged array to the specified managed array. /// /// It is important that /// The managed array to copy the unmanaged values to. /// The index to start at when copying tobe 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 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 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); } } } ///and /// is greater than the length of . /// 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 } }