SafeHandle 类

定义

表示操作系统句柄的包装类。 必须继承此类。

public ref class SafeHandle abstract : IDisposable
public ref class SafeHandle abstract : System::Runtime::ConstrainedExecution::CriticalFinalizerObject, IDisposable
[System.Security.SecurityCritical]
public abstract class SafeHandle : IDisposable
public abstract class SafeHandle : System.Runtime.ConstrainedExecution.CriticalFinalizerObject, IDisposable
[System.Security.SecurityCritical]
public abstract class SafeHandle : System.Runtime.ConstrainedExecution.CriticalFinalizerObject, IDisposable
[<System.Security.SecurityCritical>]
type SafeHandle = class
    interface IDisposable
type SafeHandle = class
    inherit CriticalFinalizerObject
    interface IDisposable
[<System.Security.SecurityCritical>]
type SafeHandle = class
    inherit CriticalFinalizerObject
    interface IDisposable
Public MustInherit Class SafeHandle
Implements IDisposable
Public MustInherit Class SafeHandle
Inherits CriticalFinalizerObject
Implements IDisposable
继承
SafeHandle
继承
派生
属性
实现

示例

下面的代码示例为从 派生的 SafeHandleZeroOrMinusOneIsInvalid操作系统文件句柄创建自定义安全句柄。 它从文件读取字节并显示其十六进制值。 它还包含导致线程中止但释放句柄值的错误测试工具。 使用 IntPtr 表示句柄时,句柄偶尔会因异步线程中止而泄漏。

需要与已编译的应用程序位于同一文件夹中的文本文件。 假设将应用程序命名为“HexViewer”,则命令行用法为:

HexViewer <filename> -Fault

(可选)指定 -Fault 以有意尝试通过中止特定窗口中的线程来泄漏句柄。 使用 Windows Perfmon.exe 工具在注入故障时监视句柄计数。

using System;
using System.Runtime.InteropServices;
using System.IO;
using System.ComponentModel;
using System.Security;
using System.Threading;
using Microsoft.Win32.SafeHandles;
using System.Runtime.ConstrainedExecution;
using System.Security.Permissions;

namespace SafeHandleDemo
{
    internal class MySafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        // Create a SafeHandle, informing the base class
        // that this SafeHandle instance "owns" the handle,
        // and therefore SafeHandle should call
        // our ReleaseHandle method when the SafeHandle
        // is no longer in use.
        private MySafeFileHandle()
            : base(true)
        {
        }
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        override protected bool ReleaseHandle()
        {
            // Here, we must obey all rules for constrained execution regions.
            return NativeMethods.CloseHandle(handle);
            // If ReleaseHandle failed, it can be reported via the
            // "releaseHandleFailed" managed debugging assistant (MDA).  This
            // MDA is disabled by default, but can be enabled in a debugger
            // or during testing to diagnose handle corruption problems.
            // We do not throw an exception because most code could not recover
            // from the problem.
        }
    }

    [SuppressUnmanagedCodeSecurity()]
    internal static class NativeMethods
    {
        // Win32 constants for accessing files.
        internal const int GENERIC_READ = unchecked((int)0x80000000);

        // Allocate a file object in the kernel, then return a handle to it.
        [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
        internal extern static MySafeFileHandle CreateFile(String fileName,
           int dwDesiredAccess, System.IO.FileShare dwShareMode,
           IntPtr securityAttrs_MustBeZero, System.IO.FileMode dwCreationDisposition,
           int dwFlagsAndAttributes, IntPtr hTemplateFile_MustBeZero);

        // Use the file handle.
        [DllImport("kernel32", SetLastError = true)]
        internal extern static int ReadFile(MySafeFileHandle handle, byte[] bytes,
           int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero);

        // Free the kernel's file object (close the file).
        [DllImport("kernel32", SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal extern static bool CloseHandle(IntPtr handle);
    }

    // The MyFileReader class is a sample class that accesses an operating system
    // resource and implements IDisposable. This is useful to show the types of
    // transformation required to make your resource wrapping classes
    // more resilient. Note the Dispose and Finalize implementations.
    // Consider this a simulation of System.IO.FileStream.
    public class MyFileReader : IDisposable
    {
        // _handle is set to null to indicate disposal of this instance.
        private MySafeFileHandle _handle;

        public MyFileReader(String fileName)
        {
            // Security permission check.
            String fullPath = Path.GetFullPath(fileName);
            new FileIOPermission(FileIOPermissionAccess.Read, fullPath).Demand();

            // Open a file, and save its handle in _handle.
            // Note that the most optimized code turns into two processor
            // instructions: 1) a call, and 2) moving the return value into
            // the _handle field.  With SafeHandle, the CLR's platform invoke
            // marshaling layer will store the handle into the SafeHandle
            // object in an atomic fashion. There is still the problem
            // that the SafeHandle object may not be stored in _handle, but
            // the real operating system handle value has been safely stored
            // in a critical finalizable object, ensuring against leaking
            // the handle even if there is an asynchronous exception.

            MySafeFileHandle tmpHandle;
            tmpHandle = NativeMethods.CreateFile(fileName, NativeMethods.GENERIC_READ,
                FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

            // An async exception here will cause us to run our finalizer with
            // a null _handle, but MySafeFileHandle's ReleaseHandle code will
            // be invoked to free the handle.

            // This call to Sleep, run from the fault injection code in Main,
            // will help trigger a race. But it will not cause a handle leak
            // because the handle is already stored in a SafeHandle instance.
            // Critical finalization then guarantees that freeing the handle,
            // even during an unexpected AppDomain unload.
            Thread.Sleep(500);
            _handle = tmpHandle;  // Makes _handle point to a critical finalizable object.

            // Determine if file is opened successfully.
            if (_handle.IsInvalid)
                throw new Win32Exception(Marshal.GetLastWin32Error(), fileName);
        }

        public void Dispose()  // Follow the Dispose pattern - public nonvirtual.
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        // No finalizer is needed. The finalizer on SafeHandle
        // will clean up the MySafeFileHandle instance,
        // if it hasn't already been disposed.
        // However, there may be a need for a subclass to
        // introduce a finalizer, so Dispose is properly implemented here.
        protected virtual void Dispose(bool disposing)
        {
            // Note there are three interesting states here:
            // 1) CreateFile failed, _handle contains an invalid handle
            // 2) We called Dispose already, _handle is closed.
            // 3) _handle is null, due to an async exception before
            //    calling CreateFile. Note that the finalizer runs
            //    if the constructor fails.
            if (_handle != null && !_handle.IsInvalid)
            {
                // Free the handle
                _handle.Dispose();
            }
            // SafeHandle records the fact that we've called Dispose.
        }

        public byte[] ReadContents(int length)
        {
            if (_handle.IsInvalid)  // Is the handle disposed?
                throw new ObjectDisposedException("FileReader is closed");

            // This sample code will not work for all files.
            byte[] bytes = new byte[length];
            int numRead = 0;
            int r = NativeMethods.ReadFile(_handle, bytes, length, out numRead, IntPtr.Zero);
            // Since we removed MyFileReader's finalizer, we no longer need to
            // call GC.KeepAlive here.  Platform invoke will keep the SafeHandle
            // instance alive for the duration of the call.
            if (r == 0)
                throw new Win32Exception(Marshal.GetLastWin32Error());
            if (numRead < length)
            {
                byte[] newBytes = new byte[numRead];
                Array.Copy(bytes, newBytes, numRead);
                bytes = newBytes;
            }
            return bytes;
        }
    }

    static class Program
    {
        // Testing harness that injects faults.
        private static bool _printToConsole = false;
        private static bool _workerStarted = false;

        private static void Usage()
        {
            Console.WriteLine("Usage:");
            // Assumes that application is named HexViewer"
            Console.WriteLine("HexViewer <fileName> [-fault]");
            Console.WriteLine(" -fault Runs hex viewer repeatedly, injecting faults.");
        }

        private static void ViewInHex(Object fileName)
        {
            _workerStarted = true;
            byte[] bytes;
            using (MyFileReader reader = new MyFileReader((String)fileName))
            {
                bytes = reader.ReadContents(20);
            }  // Using block calls Dispose() for us here.

            if (_printToConsole)
            {
                // Print up to 20 bytes.
                int printNBytes = Math.Min(20, bytes.Length);
                Console.WriteLine("First {0} bytes of {1} in hex", printNBytes, fileName);
                for (int i = 0; i < printNBytes; i++)
                    Console.Write("{0:x} ", bytes[i]);
                Console.WriteLine();
            }
        }

        static void Main(string[] args)
        {
            if (args.Length == 0 || args.Length > 2 ||
                args[0] == "-?" || args[0] == "/?")
            {
                Usage();
                return;
            }

            String fileName = args[0];
            bool injectFaultMode = args.Length > 1;
            if (!injectFaultMode)
            {
                _printToConsole = true;
                ViewInHex(fileName);
            }
            else
            {
                Console.WriteLine("Injecting faults - watch handle count in perfmon (press Ctrl-C when done)");
                int numIterations = 0;
                while (true)
                {
                    _workerStarted = false;
                    Thread t = new Thread(new ParameterizedThreadStart(ViewInHex));
                    t.Start(fileName);
                    Thread.Sleep(1);
                    while (!_workerStarted)
                    {
                        Thread.Sleep(0);
                    }
                    t.Abort();  // Normal applications should not do this.
                    numIterations++;
                    if (numIterations % 10 == 0)
                        GC.Collect();
                    if (numIterations % 10000 == 0)
                        Console.WriteLine(numIterations);
                }
            }
        }
    }
}

注解

SafeHandle 提供句柄资源的关键终结,防止垃圾回收过早回收句柄,防止 Windows 回收句柄以引用意外的非托管对象。

本主题包含下列部分:

为什么选择 SafeHandle?生自 SafeHandle 的类的作用

为什么选择 SafeHandle?

在 .NET Framework 2.0 版之前,所有操作系统句柄只能封装在托管包装器对象中IntPtr。 虽然这是与本机代码互操作的便捷方法,但句柄可能会因异步异常(例如线程意外中止或堆栈溢出)而泄露。 这些异步异常是清理操作系统资源的障碍,它们几乎可能发生在应用中的任意位置。

尽管重写 Object.Finalize 方法允许在对对象进行垃圾回收时清理非托管资源,但在某些情况下,在平台调用中执行方法时,垃圾回收可以回收可终结的对象。 如果终结器释放传递给该平台调用的句柄,则可能导致处理损坏。 在平台调用期间(例如读取文件时)方法被阻止时,也可以回收句柄。

更关键的是,由于 Windows 会主动回收句柄,因此句柄可以回收并指向可能包含敏感数据的另一个资源。 这称为回收攻击,可能会损坏数据并构成安全威胁。

SafeHandle 的作用

SafeHandle 简化了其中几个对象生存期问题,并与平台调用集成,以便操作系统资源不会泄漏。 类 SafeHandle 通过不中断地分配和释放句柄来解决对象生存期问题。 它包含一个关键终结器,可确保句柄关闭并保证在意外 AppDomain 卸载期间运行,即使在假定平台调用调用处于损坏状态的情况下也是如此。

由于 SafeHandle 继承自 CriticalFinalizerObject,因此在所有关键终结器之前调用所有非关键终结器。 对在同一垃圾回收过程中不再生存的对象调用终结器。 例如, FileStream 对象可以运行正常的终结器来刷新现有的缓冲数据,而不会有句柄泄漏或回收的风险。 关键和非关键终结器之间的这种非常弱的排序不适用于一般用途。 它的存在主要是为了通过允许这些库在不更改其语义的情况下使用这些 SafeHandle 库来帮助迁移现有库。 此外,关键终结器及其调用的任何内容(如 SafeHandle.ReleaseHandle() 方法)都必须位于受约束的执行区域中。 这会对可在终结器调用图中编写的代码施加约束。

平台调用操作会自动递增由 SafeHandle 封装的句柄的引用计数,并在完成后递减这些句柄。 这可确保不会回收或意外关闭句柄。

通过在类构造函数中的 参数提供值ownsHandle,可以在构造SafeHandle对象时指定基础句柄的SafeHandle所有权。 这控制对象 SafeHandle 是否在释放对象后释放句柄。 这对于具有特殊生存期要求的句柄或使用其生存期由其他人控制的句柄很有用。

派生自 SafeHandle 的类

SafeHandle 是操作系统句柄的抽象包装类。 从此类派生比较困难。 但可以使用 Microsoft.Win32.SafeHandles 命名空间中可提供以下项的安全句柄的派生类。

实施者说明

若要创建派生自 SafeHandle的类,必须知道如何创建和释放操作系统句柄。 此过程对于不同的句柄类型是不同的,因为有些使用 CloseHandle 函数,而另一些则使用更具体的函数,如 UnmapViewOfFileFindClose。 出于此原因,必须为要包装在安全句柄中的每个操作系统句柄类型创建派生类 SafeHandle

当从 SafeHandle 继承时,必须重写下面的成员:IsInvalidReleaseHandle()

还应提供一个公共无参数构造函数,该构造函数使用表示无效句柄值的值调用基构造函数,并提供一个 Boolean 值,该值指示本机句柄是否由 SafeHandle 拥有,因此应在释放该句柄时 SafeHandle 释放。

构造函数

SafeHandle()

表示操作系统句柄的包装类。 必须继承此类。

SafeHandle(IntPtr, Boolean)

用指定的无效句柄值初始化 SafeHandle 类的新实例。

字段

handle

指定要包装的句柄。

属性

IsClosed

获取一个值,该值指示句柄是否已关闭。

IsInvalid

在派生类中重写时,获取一个值,该值指示句柄值是否无效。

方法

Close()

标记句柄,以便释放资源。

DangerousAddRef(Boolean)

手动递增 SafeHandle 实例中的引用计数器。

DangerousGetHandle()

返回 handle 字段的值。

DangerousRelease()

手动递减 SafeHandle 实例中的引用计数器。

Dispose()

释放 SafeHandle 类使用的所有资源。

Dispose(Boolean)

释放 SafeHandle 类所使用的非托管资源,指定是否执行常规释放操作。

Equals(Object)

确定指定对象是否等于当前对象。

(继承自 Object)
Finalize()

释放与句柄关联的所有资源。

GetHashCode()

作为默认哈希函数。

(继承自 Object)
GetType()

获取当前实例的 Type

(继承自 Object)
MemberwiseClone()

创建当前 Object 的浅表副本。

(继承自 Object)
ReleaseHandle()

在派生类中重写时,执行释放句柄所需的代码。

SetHandle(IntPtr)

将句柄设置为预先存在的指定句柄。

SetHandleAsInvalid()

将句柄标记为不再使用。

ToString()

返回表示当前对象的字符串。

(继承自 Object)

适用于

另请参阅