2008-10-01 137 views
6

什么是SafeHandle?它与IntPtr有什么不同?我应该什么时候使用一个?它的优点是什么?C安全手柄#

回答

8

我觉得MSDN是定义非常明确:

SafeHandle类提供关键 定稿手柄资源, 被 防止把手垃圾 收集和由 的Windows被回收过早回收引用非预期的 非托管对象。在.NET Framework 2.0版之前,所有运行的 系统句柄只能是 封装在IntPtr管理的 包装器对象中。

SafeHandle类包含一个 终结,其确保手柄 被关闭,并保证运行, 即使在意外的应用程序域 卸载当主机可能不信任的应用程序域 的状态的 一致性。

有关使用SafeHandle的 好处的更多信息,请参阅 安全句柄和严重 完成。

这个类是抽象的,因为你的 不能创建一个通用句柄。以 执行SafeHandle,您必须创建 一个派生类。要创建派生类的SafeHandle ,您必须知道如何创建并释放操作系统 句柄 。对于 不同的句柄类型,此过程有所不同,因为某些 使用CloseHandle,而另一些则使用更多 特定方法,例如 UnmapViewOfFile或FindClose。对于此 原因,必须为每个操作的 系统句柄类型创建派生的 类SafeHandle;如 MySafeRegistryHandle, MySafeFileHandle和 MySpecialSafeFileHandle。这些 派生类中的一些会被预先编写, 会在 Microsoft.Win32.SafeHandles名称空间中为您提供。

8

看它的另一种方式:与SafeHandle的,你应该几乎永远不需要再写终结。

+0

我明白了。尽管我们需要编写一个方法来释放该句柄,覆盖ReleaseHandle()。我认为这是在最终确定过程中被调用的。 – Vivek 2008-10-01 13:37:34

2

托管代码正在从非托管代码接收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]); 
     } 
    } 
} 

/// <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'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); 
      } 
     } 
    } 

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