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파생된 운영 체제 파일 핸들에 대한 사용자 지정 안전 핸들을 만듭니다. 파일에서 바이트를 읽고 해당 16진수 값을 표시합니다. 또한 스레드가 중단되도록 하는 오류 테스트 도구가 포함되어 있지만 핸들 값이 해제됩니다. 를 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);
                }
            }
        }
    }
}

설명

이 API에 대한 자세한 내용은 SafeHandle에 대한 추가 API 설명을 참조하세요.

구현자 참고

에서 SafeHandle파생된 클래스를 만들려면 운영 체제 핸들을 만들고 해제하는 방법을 알고 있어야 합니다. 일부에서는 [CloseHandle](/windows/win32/api/handleapi/nf-handleapi-closehandle) 함수를 사용하기 때문에 이 프로세스는 다른 핸들 형식에 따라 다릅니다. 반면 다른 함수는 [UnmapViewOfFile](/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile) 또는 [FindClose](/windows/win32/api/fileapi/nf-fileapi-findclose)와 같은 보다 구체적인 함수를 사용합니다. 이러한 이유로 안전 핸들에 래핑하려는 각 운영 체제 핸들 형식에 대해 의 파생 클래스 SafeHandle 를 만들어야 합니다.

SafeHandle에서 상속하는 경우 IsInvalidReleaseHandle() 멤버를 재정의해야 합니다.

또한 잘못된 핸들 값을 나타내는 값을 사용하여 기본 생성자를 호출하는 공용 매개 변수 없는 생성자를 제공해야 하며 Boolean , 네이티브 핸들이 에 의해 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)

적용 대상

추가 정보