Safe Handles and Critical Finalization
Before the .NET Framework version 2.0, all operating system handles could be encapsulated only in the IntPtr managed wrapper object. While this was a convenient way to interoperate with native code, handles could be leaked by asynchronous exceptions, such as a thread aborting unexpectedly or a stack overflow. These asynchronous exceptions are an obstacle to cleaning up operating system resources and they can occur almost anywhere in your program. They are prone to occur in applications that use a host that is running managed code, such as Microsoft SQL Server.
In some circumstances, finalizable objects could be reclaimed by garbage collection while executing a method within a platform invoke call. If a finalizer freed the handle passed to that platform invoke call, it could lead to handle corruption. The handle could also be reclaimed while your method is blocked during a platform invoke call, such as while reading a file.
More critically, because Windows aggressively recycles handles, a handle could be recycled and point to another resource that might contain sensitive data. This is known as a recycle attack and can potentially corrupt data and be a security threat.
Starting with the .NET Framework 2.0, the SafeHandle class simplifies several of these object lifetime issues, and is integrated with platform invoke so that that operating system resources are not leaked. The SafeHandle class resolves object lifetime issues by assigning and releasing handles without interruption. It contains a critical finalizer that ensures that the handle is closed and is guaranteed to run during AppDomain unloads, even in cases when the platform invoke call is assumed to be in a corrupted state.
Because SafeHandle inherits from CriticalFinalizerObject, all the noncritical finalizers are called before any of the critical finalizers. The finalizers are called on objects that are no longer live during the same garbage collection pass. For example, a FileStream object can run a normal finalizer to flush out existing buffered data without the risk of the handle being leaked or recycled. This very weak ordering between critical and noncritical finalizers is not intended for general use. It exists primarily to assist in the migration of existing libraries by allowing those libraries to use SafeHandle without altering their semantics. Additionally, the critical finalizer and anything it calls, such as the System.Runtime.InteropServices.SafeHandle.ReleaseHandle method, must be in a constrained execution region. This imposes constraints on what code can be written within the finalizer's call graph.
Starting with the .NET Framework version 2.0, platform invoke operations automatically increment the reference count of handles encapsulated by a SafeHandle and decrement them upon completion. This ensures that the handle will not be recycled or closed unexpectedly.
You can specify ownership of the underlying handle when constructing SafeHandle objects. This controls whether the SafeHandle object will release the handle after the object has been disposed. This is useful for handles with peculiar lifetime requirements or for consuming a handle whose lifetime is controlled by someone else.
Safe Handle Classes
The Microsoft.Win32.SafeHandles namespace contains classes derived from SafeHandle to provide functionality supporting file and operating system handles. The following table summarizes the .NET Framework safe handle classes.
Class | Description |
---|---|
SafeHandle |
Wraps a handle for platform invoke operations that ensures finalization without interruption. This class must be inherited. |
Provides access to unmanaged file handles. |
|
Provides access to unmanaged wait handles. |
|
and |
Allows you to create a custom safe handle class. |