2008-10-01 33 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 
    } 
}