Safehandle in C#

前端 未结 3 676
天涯浪人
天涯浪人 2021-01-11 09:52

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

3条回答
  •  难免孤独
    2021-01-11 10:22

    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.SafeBuffer also 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.

    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 CriticalFinalizerObject class. 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.

    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.

    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 LogonUser is 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 CommandLineToArgvW to 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.

    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.
            /// 
            /// true if the function succeeds, false otherwise. To get extended
            /// error information, call .
            protected override bool ReleaseHandle()
            {
                return NativeMethods.CloseHandle(this.handle);
            }
        }
    
        /// 
        /// 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  as a string.
            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.
        /// 
        /// 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.
            /// true to reliably release the handle during the finalization phase;
            /// false to prevent reliable release (not recommended).
            /// 
            protected SafeLocalAllocArray(bool ownsHandle)
                : base(ownsHandle)
            {
            }
    
            /// 
            /// Converts the unmanaged object referred to by  to a managed object
            /// of type T.
            /// 
            /// The index of the value to retrieve.
            /// the value at the position specified by  as a managed object of
            /// type T.
            protected abstract T GetArrayValue(int index);
    
            // 
            /// 
            /// Frees the wrapped array by calling LocalFree.
            /// 
            /// true if the call to LocalFree succeeds, false if the call fails.
            protected override bool ReleaseHandle()
            {
                return (NativeMethods.LocalFree(this.handle) == IntPtr.Zero);
            }
    
            /// 
            /// Copies the unmanaged array to the specified managed array.
            /// 
            /// It is important that the length of  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 managed array to copy the unmanaged values 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.
            public void CopyTo(T[] array)
            {
                if (array == null)
                {
                    throw new ArgumentNullException("array");
                }
    
                this.CopyTo(array, 0, array.Length);
            }
    
            /// 
            /// Copies the unmanaged array to the specified managed array.
            /// 
            /// It is important that  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  will be thrown.
            /// 
            /// The managed array to copy the unmanaged values to.
            /// The index to start at when copying to .
            /// 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  and 
            /// is greater than the length 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);
                }
            }
        }
    
        /// 
        /// 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
        }
    }
    

提交回复
热议问题