SafeHandle 類別

定義

表示作業系統控制代碼的包裝函式類別 (Wrapper Class)。 這個類別必須被繼承。

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 的類別

為什麼 SafeHandle?

在 .NET Framework 2.0 版之前,所有作業系統控制碼只能封裝在 Managed 包裝函式物件中 IntPtr 。 雖然這是與原生程式碼互通的便利方式,但控制碼可能會因為非同步例外狀況而外泄,例如執行緒意外中止或堆疊溢位。 這些非同步例外狀況是清除作業系統資源的障礙,而且幾乎可以在您的應用程式中的任何位置發生。

雖然方法的 Object.Finalize 覆寫允許在垃圾收集物件時清除 Unmanaged 資源,但在某些情況下,垃圾收集可以回收可完成的物件,同時在平台叫用呼叫內執行方法。 如果完成項釋放傳遞至該平台叫用呼叫的控制碼,可能會導致處理損毀。 當您的方法在平台叫用呼叫期間遭到封鎖時,也可以回收控制碼,例如讀取檔案時。

更重要的是,因為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 類別所使用的 Unmanaged 資源,指定是否要執行一般處置作業。

Equals(Object)

判斷指定的物件是否等於目前的物件。

(繼承來源 Object)
Finalize()

釋放與控制代碼相關的所有資源。

GetHashCode()

做為預設雜湊函式。

(繼承來源 Object)
GetType()

取得目前執行個體的 Type

(繼承來源 Object)
MemberwiseClone()

建立目前 Object 的淺層複製。

(繼承來源 Object)
ReleaseHandle()

在衍生類別中覆寫時,執行釋放控制代碼所需的程式碼。

SetHandle(IntPtr)

將控制代碼設定為指定的既有控制代碼。

SetHandleAsInvalid()

將控制代碼標記為不再使用。

ToString()

傳回代表目前物件的字串。

(繼承來源 Object)

適用於

另請參閱