我认为MSDN的定义非常明确:
SafeHandle类提供了句柄资源的关键最终化,防止句柄被垃圾收集过早地回收,并被Windows回收以引用非预期的非托管对象.在.NET Framework 2.0版之前,所有操作系统句柄只能封装在IntPtr托管包装器对象中.
SafeHandle类包含一个终结器,可确保句柄关闭并保证运行,即使在主机可能不信任AppDomain状态的一致性时意外的AppDomain卸载期间也是如此.
有关使用SafeHandle的好处的更多信息,请参阅安全句柄和关键终结.
此类是抽象的,因为您无法创建通用句柄.要实现SafeHandle,您必须创建派生类.要创建SafeHandle派生类,您必须知道如何创建和释放操作系统句柄.对于不同的句柄类型,此过程是不同的,因为一些使用CloseHandle,而其他使用更具体的方法,如UnmapViewOfFile或FindClose.因此,您必须为每个操作系统句柄类型创建一个SafeHandle派生类; 例如MySafeRegistryHandle,MySafeFileHandle和MySpecialSafeFileHandle.其中一些派生类是在Microsoft.Win32.SafeHandles命名空间中预先编写并提供的.
小智 6
如果托管代码从非托管代码接收到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
}
}
Run Code Online (Sandbox Code Playgroud)