本文件已封存並已停止維護。

SafeHandle 類別

更新:2007 年 11 月

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

命名空間:  System.Runtime.InteropServices
組件:  mscorlib (在 mscorlib.dll 中)

[SecurityPermissionAttribute(SecurityAction.LinkDemand, UnmanagedCode = true)]
[SecurityPermissionAttribute(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
public abstract class SafeHandle : CriticalFinalizerObject, 
	IDisposable
/** @attribute SecurityPermissionAttribute(SecurityAction.LinkDemand, UnmanagedCode = true) */
/** @attribute SecurityPermissionAttribute(SecurityAction.InheritanceDemand, UnmanagedCode = true) */
public abstract class SafeHandle extends CriticalFinalizerObject implements IDisposable
public abstract class SafeHandle extends CriticalFinalizerObject implements IDisposable

SafeHandle 類別提供控制代碼資源的關鍵結束,以避免記憶體回收不當回收控制代碼,或避免 Windows 回收控制代碼來參考不想要的 Unmanaged 物件。在.NET Framework 2.0 版之前,所有的作業系統控制項都只能封裝在 IntPtr Managed 包裝函式物件中。

SafeHandle 類別包含完成項,即使在主機可能不信任 AppDomain 狀態的一致性時發生未預期的 AppDomain 卸載期間,此完成項仍可確保控制代碼已關閉,也可保證控制代碼一定會執行。

如需使用 SafeHandle 之優點的詳細資訊,請參閱安全控制代碼和關鍵結束

由於您無法建立泛型控制代碼,因此這個類別是抽象的。若要實作 SafeHandle,您必須建立衍生類別 (Derived Class)。若要建立 SafeHandle 衍生類別,您必須知道如何建立及釋放作業系統控制代碼。這項程序會因控制代碼型別的不同而異,因為某些型別會使用 CloseHandle,而某些型別則使用更特定的方法,例如 UnmapViewOfFileFindClose。基於這項理由,您必須針對每個作業系統控制代碼型別 (例如 MySafeRegistryHandleMySafeFileHandleMySpecialSafeFileHandle),建立 SafeHandle 的衍生類別。在 Microsoft.Win32.SafeHandles 命名空間中已為您預先寫入並提供了其中某些衍生類別。

注意事項:

自行撰寫衍生自 SafeHandle 的類別是進階的程式設計功能。一組衍生自 SafeHandle 的預先寫入類別已當做抽象衍生提供,而且這組類別位於 Microsoft.Win32.SafeHandles 命名空間中。設計這些類別的用意是為了提供支援檔案和作業系統控制代碼的通用功能。

繼承者注意事項

SafeHandle 繼承時,您必須覆寫下列成員:IsInvalidReleaseHandle

此外,您還應該提供預設建構函式 (Constructor) (它會使用表示無效控制代碼值的值,呼叫基底建構函式),以及Boolean (Boolean) 值,指出原生控制代碼是否將由 SafeHandle 程式碼所擁有,以及是否因此而在該 SafeHandle 已經處置 (Dispose) 時釋出。

下列程式碼範例會建立衍生自 SafeHandleZeroOrMinusOneIsInvalid 的自訂安全控制代碼,以做為作業系統檔案控制代碼 (File Handle)。此控制代碼會從檔案讀取位元組,並顯示其十六進位值;它也包含造成執行緒中止,但是會釋出控制代碼值的錯誤測試控管。使用 IntPtr 來表示控制代碼時,控制代碼偶而會因為非同步的 (Asynchronous) 執行緒中止而發生遺漏。

您必須在編譯完成的應用程式的相同資料夾中包含有一個文字檔。假設您將應用程式命名為 "HexViewer",命令列的使用方式將為:

HexViewer <filename> -Fault

選擇性地指定 -Fault,透過中止特定視窗中的執行緒,以刻意嘗試遺漏控制代碼。插入錯誤時,請使用 Windows Perform.exe 工具監視控制代碼計數。

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

namespace SafeHandleDemo
{
    [SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
    [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
    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(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.
        // Howerver, there may be a need for a subclass to
        // introduce a finalizer, so Dispose is properly implemented here.
        [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
        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.
        }


        [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
        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 HexViwer"
            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);
                }
            }

        }
    }
}


這個型別的任何 Public static (在 Visual Basic 中為 Shared) 成員都具備執行緒安全。並非所有的執行個體成員都是安全執行緒。

Windows Vista, Windows XP SP2, Windows XP Media Center Edition, Windows XP Professional x64 Edition, Windows XP Starter Edition, Windows Server 2003, Windows Server 2000 SP4, Windows Millennium Edition, Windows 98

.NET Framework 和 .NET Compact Framework 並不支援各種平台的所有版本。如需支援平台版本的相關資訊,請參閱 .NET Framework 系統需求

.NET Framework

支援版本:3.5、3.0、2.0
顯示: