Stop using IntPtr for dealing with system handles
When using system handles such as file handles, process handles, or any other handles provided by the kernel, you should take care to release them correctly when you don't need them anymore. The native APIs often provides a method to get a handle and a method to release it, plus sometimes a few methods to work with the resource. For instance, you can get a file handle using the method CreateFile
, release it using CloseHandle
, and for instance, write to the file using WriteFile
. If you don't want to leak the resource, you need to ensure you call the method CloseHandle
as soon as you have finished using the file. You must call this method once and only once. What if there is an exception in the code before you could release the handle. Maybe the exception is not directly from your code, but for instance, someone could abort the thread you are running on, or you could run out of memory.
SafeHandle
was introduced in .NET but it is still not commonly used (except in the .NET Framework of course). SafeHandle provides many advantages:
- The finalizer logic is already implemented, preventing you from doing mistakes while implementing them. Also, they inherit from
CriticalFinalizerObject
making them more reliable than other objects (could be freed even whenThreadAbortException
orOutOfMemoryException
are raised). - The objects that own a
SafeHandle
do not need to have a finalizer, so they are easier to write. - Objects with finalizer have special treatments in the Garbage Collector. You should keep them as small as possible to avoid promoting a large object graph due to finalization. SafeHandles are minimal wrappers around unmanaged resources, so it reduce the issue.
- SafeHandles are well-integrated with P/Invoke. You can use
SafeHandle
-derived class in the definition of the method instead ofIntPtr
, so they are strongly-typed. - SafeHandles are well-integrated with the Garbage Collector. You don't need to use
HandleRef
andGC.KeepAlive
explicitly when calling a native method, the CLR will do what is needed to avoid finalizing theSafeHandle
while calling a native method. - SafeHandles prevent handle-recycling vulnerability: Lifetime, GC.KeepAlive, handle recycling
SafeHandle
is an abstract class, so you need to inherit from it. When you inherit from SafeHandle
, you must override the following members: IsInvalid
and ReleaseHandle
. You should also provide a default constructor that calls the base constructor with a value that represents an invalid handle value, and a boolean value indicating whether the native handle is owned by the SafeHandle
and consequently should be freed when that SafeHandle
has been disposed. Note that the ReleaseHandle
method is guaranteed to be called only once and only if the handle is valid as defined by the IsInvalid
property. You can also inherit from SafeHandleZeroOrMinusOneIsInvalid
which handles the invalid handle.
// inherits from SafeHandleZeroOrMinusOneIsInvalid, so IsInvalid is already implemented.
internal sealed class MySafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// A default constructor is required for P/Invoke to instantiate the class
public MySafeHandle()
: base(ownsHandle: true)
{
}
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(handle);
}
}
internal static class NativeMethods
{
// Returns the SafeHandle instead of IntPtr
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
internal extern static MySafeHandle CreateFile(String fileName, int dwDesiredAccess, System.IO.FileShare dwShareMode, IntPtr securityAttrs_MustBeZero, System.IO.FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr TemplateFile_MustBeZero);
// Take a SafeHandle in parameter instead of IntPtr
[DllImport("kernel32", SetLastError = true)]
internal extern static int ReadFile(MySafeHandle handle, byte[] bytes, int numBytesToRead, out int numBytesRead, IntPtr overlapped_MustBeZero);
[DllImport("kernel32", SetLastError = true)]
internal extern static bool CloseHandle(IntPtr handle);
}
As all the hard work is done by the SafeHandle
, your code is much simpler! Here's an example of usage of MySafeHandle
:
public sealed class MyFileWrapper : IDisposable
{
private readonly MySafeHandle _handle;
public MyFileWrapper(string fullPath)
{
_handle = NativeMethods.CreateFile(fullPath, ...);
}
// - There is no need to implement a finalizer, MySafeHandle already has one
// - You do not need to protect against multiple disposing, MySafeHandle already does
public void Dispose()
{
_handle.Dispose();
}
}
#Guidelines
When working with unmanaged resources, you should consider:
- Using an existing
SafeHandle
if possible - If not possible, subclass
SafeHandle
to create one that meets your needs. This class should not do anything more than managing unmanaged resources. It should be sealed. - If that's not possible, create your class which implements
IDisposable
and a finalizer- The class should be sealed
- If sealing the class is not possible, add a method
protected void Dispose(bool disposing)
, so subclasses can implements the dispose pattern correctly.
#Resources
- SafeHandle by Shawn Farkas
- Finalization by cbrumme
- SafeHandle documentation
- SafeHandles: the best V2.0 feature of the .NET Framework
- GrabYourPitchforks's comment about finalizers
Do you have a question or a suggestion about this post? Contact me!