C#中的安全句柄

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/155780/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 16:01:24  来源:igfitidea点击:

Safehandle in C#

提问by Vivek

What is SafeHandle? how does it differ from IntPtr? When should I use one? What are its advantages?

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

采纳答案by aku

I think MSDNis pretty clear in definition:

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

The SafeHandle class provides critical finalization of handle resources, preventing handles from being reclaimed prematurely by garbage collection and from being recycled by Windows to reference unintended unmanaged objects. Before the .NET Framework version 2.0, all operating system handles could only be encapsulated in the IntPtr managed wrapper object.

The SafeHandle class contains a finalizer that ensures that the handle is closed and is guaranteed to run, even during unexpected AppDomain unloads when a host may not trust the consistency of the state of the AppDomain.

For more information about the benefits of using a SafeHandle, see Safe Handles and Critical Finalization.

This class is abstract because you cannot create a generic handle. To implement SafeHandle, you must create a derived class. To create SafeHandle derived classes, you must know how to create and free an operating system handle. This process is different for different handle types because some use CloseHandle, while others use more specific methods such as UnmapViewOfFile or FindClose. For this reason, you must create a derived class of SafeHandle for each operating system handle type; such as MySafeRegistryHandle, MySafeFileHandle, and MySpecialSafeFileHandle. Some of these derived classes are prewritten and provided for you in the Microsoft.Win32.SafeHandles namespace.

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

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

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

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

回答by Jon Skeet

Another way of looking at it: with SafeHandle, you should almostnever need to write another finalizer.

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

回答by Mike Ness

You should use a derivative of SafeHandle whenever possible where managed code is receiving an IntPtr from unmanaged code. While the name, general use, and even documentation of the SafeHandle class implies that it is only supposed to be used to contain Windows operating system handles, a few internal .NET framework classes such as Microsoft.Win32.SafeHandles.SafeLocalAllocHandle and those that derive from the publicly available abstract class System.Runtime.InteropServices.SafeBufferalso use it to guarantee that other unmanaged resources such as dynamically allocated structs and arrays are freed. In general, I believe that it is good practice to create a derivative of this class whenever an IntPtr is returned to managed code from unmanaged code even if it doesn't require cleanup.

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

The established purpose of a SafeHandle is to guarantee that even if the world is ending (e.g. an AppDomain is being unloaded or a StackOverflowException occurs) the .NET framework should make absolutely sure that the finalizer for the SafeHandle is called to close or deallocate the unmanaged entity being referred to by the wrapped IntPtr. The SafeHandle class achieves this by inheriting from the CriticalFinalizerObjectclass. Inheriting from this class does, however, place upon the inheritor the obligation of not totally screwing up the state of the process when the finalizer is called, which is likely why it is not often used for entities other than Windows operating system handles. The .NET framework also provides some weak finalization ordering so that it is safe to interact with a SafeHandle object in the finalizer of any class that does not inherit from CriticalFinalizerObject, but circumstances in which that is necessary should be few and far between.

SafeHandle 的既定目的是保证即使世界即将结束(例如 AppDomain 正在卸载或 StackOverflowException 发生),.NET 框架也应该绝对确保 SafeHandle 的终结器被调用以关闭或释放非托管被包装的 IntPtr 引用的实体。SafeHandle 类通过从CriticalFinalizerObject继承来实现这一点班级。然而,从这个类继承确实给继承者带来了在调用终结器时不完全搞砸进程状态的义务,这可能是为什么它不经常用于 Windows 操作系统句柄以外的实体的原因。.NET 框架还提供了一些弱终结排序,以便与不从 CriticalFinalizerObject 继承的任何类的终结器中的 SafeHandle 对象进行交互是安全的,但必要的情况应该很少。

Ideally, a SafeHandle-derived class should also be used to more safely interact with an unmanaged entity reference by encapsulating expected functionality within the derived class. A well-written class that inherits from SafeHandle should have a specific purpose in mind and should provide methods that are sufficient to prevent any developer using it for that purpose from ever needing to interact directly with the IntPtr it contains. Adding such methods also provides other developers with a clear idea of what the result of an unmanaged method call is to be used for in a managed context. A class that inherits from SafeHandle can be used for this even if no cleanup is required on the pointer that the unmanaged method returns by calling base(false) in the constructor for the class.

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

Two examples that use classes which derive from SafeHandle to safely clean up a reference to an unmanaged entity and encapsulate functionality related to the unmanaged entity are below. The first example is a more traditional scenario in which a user token returned by LogonUseris wrapped by an instance of the SafeTokenHandle class. This class will call CloseHandle on the token when the object is disposed or finalized. It also includes a method called GetWindowsIdentity that returns a WindowsIdentity object for the user represented by the user token. The second example uses Windows built-in function CommandLineToArgvWto parse a command line. This function returns a pointer to an array contained a contiguous block of memory that can be freed by a single call to LocalFree. The SafeLocalAllocWStrArray class (which inherits from class SafeLocalAllocArray which is also defined in this example) will call LocalFree on the array when object is disposed or finalized. It also includes a function that will copy the contents of the unmanaged array to a managed array.

下面的两个示例使用派生自 SafeHandle 的类来安全地清除对非托管实体的引用并封装与非托管实体相关的功能。The first example is a more traditional scenario in which a user token returned by LogonUseris wrapped by an instance of the SafeTokenHandle class. 当对象被释放或完成时,此类将在令牌上调用 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
    }
}