Click here to Skip to main content
15,860,844 members
Articles / Programming Languages / C#

IDisposable: What Your Mother Never Told You About Resource Deallocation

Rate me:
Please Sign up or sign in to vote.
4.94/5 (157 votes)
10 Nov 2014BSD37 min read 451.1K   362   67
One difficulty of the IDisposable interface overcome with the Disposable Design Principle.

Introduction - Recommended Reading

A lot of the information regarding .NET internals in this article was gleaned from "CLR via C#", 2nd Ed., by Jeffrey Richter of Wintellect, Microsoft Press. If you don't already own it, buy it. Now. Seriously. It's an essential resource for any C# programmer.

The Boring Stuff - IDisposable Gone Awry

This article is divided into two parts: the first part is an evaluation of many of the problems inherent with IDisposable, and the second part develops some "best practices" when writing IDisposable code.

Deterministic Resource Deallocation - Its Necessity

During more than 20 years of coding, I have occasionally found it necessary to design my own small programming languages for certain tasks. These have varied from imperative scripting languages to a specialized regex for trees. There are many choices to make in language design: a few simple rules should never be broken, and there are many general guidelines. One of these guidelines is:

Never design a language with exceptions that do not also have deterministic resource deallocation.

Guess which guideline the .NET runtime (and, by extension, every .NET language) does not follow? This article will examine some of the consequences of this choice, and then propose some best practices to try to work with this situation.

The reason for the guideline is that deterministic resource deallocation is necessary to produce usable programs with maintainable code. Deterministic resource deallocation provides a certain point at which a coder may know that the resource has been deallocated. There are two approaches to writing reliable software: the traditional approach deallocates resources as soon as possible, while a modern approach lets resource deallocation occur at an indeterminate time. The advantage to the modern approach is that programmers do not have to deallocate resources explicitly. The disadvantage is that it becomes much more difficult to write reliable software; an entire new set of difficult-to-test error conditions is added to the allocation logic. Unfortunately, the .NET runtime was designed around the modern approach.

The .NET runtime's support for indeterministic resource deallocation is through the Finalize method, which has special meaning to the runtime. Microsoft has also recognized the need for deterministic resource deallocation, and has added the IDisposable interface (and other helper classes which we will look at later). However, the runtime views IDisposable as just another interface, without any special meaning; and this reduction in status to second-class citizen causes some difficulties.

In C#, a "poor man's deterministic deallocation" may be implemented through the use of try and finally, or the not-much-cleaner equivalent of using. There was a lot of debate in Microsoft whether to have reference-counted references or not, and I personally believe they made the wrong decision. As a result, deterministic resource deallocation requires the use of either the awkward finally/using, or the error-prone direct call to IDisposable.Dispose. Neither of these are particularly attractive to the programmer of reliable software who is used to, e.g., C++'s shared_ptr<T>.

The IDisposable Solution

IDisposable is Microsoft's solution for allowing deterministic resource deallocation. It is intended to be implemented in the following use cases:

  • Any type that owns managed (IDisposable) resources. Note that the containing type must own the resources, not just refer to them. One difficulty here is that it is not obvious which classes implement IDisposable, requiring programmers to constantly look up each class in the documentation. FxCop is a help in this situation, and will flag the programmer's code if they forget.
  • Any type that owns unmanaged resources.
  • Any type that owns both managed and unmanaged resources.
  • Any type that derives from an IDisposable class. I don't recommend deriving from types that own unmanaged resources; it is usually a cleaner object-oriented design to have such types as fields rather than base types.

IDisposable does provide for deterministic deallocation; however, it also comes with its own set of problems.

IDisposable's Difficulties - Usability

IDisposable objects are cumbersome to use correctly in C#. Anyone who uses an IDisposable object locally must know to wrap it in a using construct. What makes this awkward is that C# does not allow using to wrap an object that isn't IDisposable. So, for every object being used in a deterministic program, the coder must determine if using is necessary, either from continual documentation lookups, or by wrapping everything in using and removing the ones causing compilation errors.

C++ is slightly better in this regard. They support stack semantics for reference types, which has a logic equivalent to inserting a using only if necessary. C# would benefit from allowing using to wrap non-IDisposable objects.

This problem with IDisposable is an end-user problem: it can be mitigated through code analysis tools and coding conventions, although there is no perfect solution. Adding to this problem is the fact that if resources are disposed at an indeterminate time (i.e., the coder forgot using when it was required), then the code may work fine during testing, and inexplicably fail in the field.

Depending on IDisposable instead of reference-counted references also brings up the problem of ownership. When C++'s reference-counted shared_ptr<T>'s last reference goes out of scope, then its resources are disposed immediately. In contrast, IDisposable-based objects place the burden on the end-user to explicitly define which code "owns" the object, and is therefore responsible for disposing of its resources. Sometimes, ownership is obvious: when an object is owned by another object, the container object also implements IDisposable and is, in turn, disposed by its owner. In another very common case, the lifetime of an object can be defined by its code scope at some point in the program, and the end-user places a using construct to define the block of code that "owns" that object. However, there are a few other cases where the object lifetime may be shared by different code paths, and these are more difficult for the end-user to code correctly (whereas a reference-counted reference would give this problem a simple solution).

IDisposable's Difficulties - Backwards Compatibility

Adding IDisposable to or removing IDisposable from an interface or class is a breaking change. Proper design implies that the client code will primarily use interfaces, so IDisposable could be added to an internal class in that situation, bypassing the interface. However, this can still cause a problem with older client code.

Microsoft ran into this problem themselves. IEnumerator did not derive from IDisposable; however, IEnumerator<T> did. Now, when older client code expecting the IEnumerable collections is given IEnumerable<T> collections instead, their enumerators are not correctly disposed.

Is this the end of the world? Probably not, but it does open up some questions about how the second-class citizen status of IDisposable influences design choices.

IDisposable's Difficulties - Design

The single greatest drawback caused by IDisposable in the area of code design is this: every interface designed must predict whether or not their derived types will need IDisposable. The actual quote is, "Implement the Dispose design pattern on a base type that commonly has derived types that hold onto resources, even if the base type does not" (from Implementing Finalize and Dispose to Clean Up Unmanaged Resources). From a design perspective, interfaces need to predict whether implementations of that interface will need IDisposable.

If an interface does not inherit from IDisposable, yet one of its implementations needs it (e.g., an implementation from a third-party vendor), then the end-user code encounters a "slicing" problem. The end-user code does not realize that the class it is using needs to be disposed. The end-user code may cope with this problem by testing objects it is using for the IDisposable interface explicitly; however, this changes the awkward using into a truly horrible-looking finally construct for each and every abstract local object. Placing this kind of burden on the end user is, in my opinion, unacceptable (at least without language support).

This "slicing" problem is the generalization of the problem Microsoft had when updating IEnumerator; they decided that enumerators often need to free resources, so they added IDisposable to IEnumerator<T>. However, adding IDisposable to a derived type can result in slicing when the end-user code uses the old IEnumerator interface.

For some interfaces, it's rather obvious whether their derived types will need IDisposable. For other interfaces, however, it's not nearly as clear, and yet the decision must be made when the interface is published. In the general case, this is a real problem.

In short, IDisposable prevents designing reusable software. The underlying problem causing all this difficulty is the violation of one of the central principles of Object Oriented Design: keep the interface separate from the implementation. The type of resources allocated and deallocated internally by an implementation of an interface should be an implementation detail. However, when Microsoft made the decision to only support IDisposable as a second-class citizen, they were deciding to view the deallocation of resources as an interface instead of an implementation detail. They were wrong, and these difficulties are the result of violating the principle of separation.

There is one rather unattractive solution: have every interface and every class descend from IDisposable. Since IDisposable may be implemented as a noop, it really only means that the derived interface or class may have an implementation, derived class, or future version that does deallocate resources. I personally have not been brave enough to embrace this as a design guideline — yet.

The final difficulty regarding the IDisposable design is how they interact with collections. Since IDisposable is an interface, either collections must behave differently when they "own" their items, or the end-user must remember to invoke IDisposable.Dispose explicitly, when necessary. If this responsibility is placed on the collection classes, then that would imply a new set of collection classes that "own" their items; and duplicating a class hierarchy is a red flag to any designer, indicating that there is something wrong. If .NET supported reference-counted references (i.e., IDisposable as a first-class citizen), then none of this would be a problem.

IDisposable's Difficulties - Additional Error State

Another difficulty with IDisposable is that it is explicitly invokeable, and not tied to the lifetime of the object. In particular, this adds a new "disposed" state to every disposable object. With the addition of this state, Microsoft recommends that every type implementing IDisposable will check in every method and property accessor to see if it has been disposed, and throw an exception if it has. Ewww... This reminds me of a co-worker I once had who insisted that we run memory checksum algorithms every time we allocate memory, "just in case" the RAM is about to fail. In my opinion, checking for the disposed state is just a waste of cycles, and would only be useful in debug code. If end users can't follow even the most basic software contracts, they won't ever produce working code anyway.

Instead of checking for the disposed state and throwing an exception, I recommend supporting "undefined behaviour". Accessing a disposed object is the modern equivalent of accessing deallocated memory.

IDisposable's Difficulties - No Guarantees

Since IDisposable is just an interface, objects implementing IDisposable only support deterministic deallocation; they cannot require it. Since it is considered perfectly acceptable for the end-user to not dispose of an object (a convention I disagree with), any IDisposable object must support extra logic to handle indeterministic deallocation as well as deterministic deallocation. Once again, indeterministic deallocation is the standard that every .NET object must support, and deterministic deallocation is just an optional addition that cannot be enforced. True enforcement of deterministic resource deallocation would require reference-counted references.

IDisposable's Difficulties - Complexity of Implementation

Microsoft has a code pattern for implementing IDisposable. Not many coders fully understand this code (e.g. why a call to GC.KeepAlive is necessary, and why synchronization of the disposed field is not). There are several other articles that describe in detail how to implement this code pattern. The following are the reasons behind its somewhat obscure design:

  • It's possible that IDisposable.Dispose may never be called, so the disposable object must include a finalizer that disposes resources. In other words, deterministic deallocation must support indeterministic deallocation.
  • IDisposable.Dispose may be called multiple times without adverse side-effects, so any real disposal code needs to be protected by a check that it has not already run.
  • Because finalizers are run on all unreachable objects in an arbitrary order, finalizers may not access managed objects. Therefore, the resource deallocation method must be able to handle both a "normal" dispose (when called from IDisposable.Dispose) and an "unmanaged-only" dispose (when called from Object.Finalize).
  • Since finalizers run on a separate thread(s), there is the possibility that the finalizer may be called before IDisposable.Dispose returns. Judicial use of GC.KeepAlive and GC.SuppressFinalize may be used to prevent race conditions.

In addition, the following facts are often overlooked:

  • Finalizers are called if constructors throw an exception (another convention I disagree with); therefore, the disposal code must gracefully handle partially-constructed objects.
  • Implementing IDisposable on a CriticalFinalizerObject-derived type is tricky because void Dispose(bool disposing) is virtual, yet it must run within a Constrained Execution Region. This may require an explicit call to RuntimeHelpers.PrepareMethod.

The naming convention for the recommended IDisposable code pattern is confusing at best. The object is implementing the interface IDisposable, and needs a boolean field disposed. So far so good, but then: as part of the code pattern, the implementation of IDisposable.Dispose calls the overloaded Dispose with a boolean disposing parameter that indicates what type of disposing can take place (not whether disposing is taking place). The naming conventions for this code pattern are confusing, even for veteran C# programmers, if they aren't constantly reviewing the code pattern. Any deviation is flagged by FxCop as a violation of this code pattern.

This is one area where C++ again has a bit of an edge on C#. The C++ compiler does much of the work of implementing IDisposable due to its destructor syntax. C# is a pure .NET language, so in many ways, has a more natural syntax than C++. However, C++ does have two helpful advantages when it comes to deterministic resource deallocation: destructor syntax, and stack semantics for reference types.

Even for the perfect programmer, the complexity of the recommended IDisposable code pattern increases the possibility of incorrect code written in libraries or by coworkers. It is a natural impulse to place shutdown logic within a Dispose method. However, since we cannot touch managed objects when in finalizers, and since IDisposable only supports deterministic deallocation rather than enforcing it, this is often a mistake. The recommended IDisposable code pattern can only be used to deallocate resources; it cannot be used to support general shutdown logic. This fact is all too often forgotten.

Sometimes, for simplicity or by accident, IDisposable is just forgotten. FxCop catches some of these violations (such as the common case where an object contains an IDisposable object), but it misses other cases. Microsoft coders themselves have fallen into this trap: WeakReference does not implement IDisposable, and it certainly should. Programmers who do not believe in the necessity of deterministic resource deallocation may simply ignore IDisposable altogether.

IDisposable's Difficulties - Impossibility of Shutdown Logic (Managed Finalization)

Shutdown logic is a common need in any real-world application. This is particularly true in asynchronous programming models. For example, a class that has its own child thread would wish to stop that thread by setting a ManualResetEvent. While it is perfectly reasonable and expected to do this when IDisposable.Dispose is called directly, this would be disastrous if called from a finalizer. Since IDisposable does not enforce deterministic deallocation, there is not even a warning to the end-user who forgets or neglects to call IDisposable.Dispose; their program just slowly leaks resources. This raises the question: is there any way for the finalizer code to access managed objects?

In order to better understand the restrictions on finalizers, we must understand the garbage collector. [Note: The description of garbage collection and finalization given here is a simplification; the actual implementation is considerably more complicated. Generations, resurrection, weak references, and several other topics are ignored. For the purpose of this article, however, this logical description is correct and reasonably complete.]

The .NET garbage collector utilizes a mark/sweep algorithm. Specifically, it does the logical equivalent of the following:

  1. Suspends all threads (except the finalizer thread(s)).
  2. Creates a set of "root" objects. If the AppDomain is unloading or the CLR is shutting down, then there are no root objects. For normal garbage collections, the root objects are:
    • Static fields.
    • Method parameters and local variables for the whole call stack of each thread, unless the current CLI instruction has already moved past the point of their last access (e.g. if a local variable is only used for the first half of a function, then it is eligible for garbage collection for the second half of the function) - note that the this pointer is included here.
    • Normal and pinned GCHandle table entries (these are used for Interop code, so the GC doesn't remove objects that are referenced only by unmanaged code).
  3. Recursively marks each root object as "reachable": for each reference field in the reachable object, the object referred to by the field is recursively marked as reachable (if it isn't already).
  4. Identifies the remaining, unmarked objects as "unreachable".
  5. Recursively marks each unreachable object with a finalizer (that hasn't had GC.SuppressFinalize called on it) as reachable, and places them on the "finalization reachable queue" in a mostly-unpredictable order.

In parallel with the garbage collection above, finalization is also constantly running in the background:

  1. A finalizer thread takes an object from the finalization reachable queue, and executes its finalizer - note that multiple finalizer threads may be executing finalizers for different objects at any given time.
  2. The object is then ignored; if it is still reachable from another object on the finalization reachable queue, then it will be kept in memory; otherwise, it will be considered unreachable, and will be collected at the next garbage collection sweep.

The reason that finalizers may not access managed objects is because they do not know what other finalizers have been run. Any object that has fields to other objects may access those fields, since the other objects will still be in memory, but nothing may be done with them, since they may have already had their finalizers run (thus disposing them). Even calling Dispose would be an error, since Dispose may already be running in the context of another finalizer thread. Calling Dispose would be meaningless anyway, since those objects are either reachable from live code, or are already on the finalization reachable queue. Also note that in the case of the AppDomain unloading or the CLR shutting down, all objects become eligible for garbage collection, including CLR runtime support objects and static reference fields; not even static methods such as EventLog.WriteEntry may be called in this situation.

There are a handful of exceptions where finalizers may access managed objects:

  • The finalization reachable queue is partially ordered: finalizers for CriticalFinalizerObject-derived types are called after finalizers for non-CriticalFinalizerObject-derived types. This means that, e.g. a class with a child thread may call ManualResetEvent.Set for a contained ManualResetEvent, as long as the class does not derive from CriticalFinalizerObject.
  • The Console object and some methods on the Thread object are given special consideration. This explains why example programs can create an object calling Console.WriteLine in its finalizer and then exit, but the same program won't work with EventLog.WriteEntry.

Generally speaking, finalizers may not access managed objects. However, support for shutdown logic is necessary for reasonably-complex software. The Windows.Forms namespace handles this with Application.Exit, which initiates an orderly shutdown. When designing library components, it is helpful to have a way of supporting shutdown logic integrated with the existing logically-similar IDisposable (this avoids having to define an IShutdownable interface without any built-in language support). This is usually done by supporting orderly shutdown when IDisposable.Dispose is invoked, and an abortive shutdown when it is not. It would be even better if the finalizer could be used to do an orderly shutdown whenever possible.

Microsoft came up against this problem, too. The StreamWriter class owns a Stream object; StreamWriter.Close will flush its buffers and then call Stream.Close. However, if a StreamWriter was not closed, its finalizer cannot flush its buffers. Microsoft "solved" this problem by not giving StreamWriter a finalizer, hoping that programmers will notice the missing data and deduce their error. This is a perfect example of the need for shutdown logic.

A Brief Hiatus - Where We Are

"But, of course, this is all far too messy. It runs counter to the goals for our new managed platform to force developers to worry about this sort of thing." (Chris Brumme, "Lifetime, GC.KeepAlive, handle recycling", blog post 2003-04-19)

<sarcasm strength="mild">Ahh, yes. .NET sure has made it easy. Why, unmanaged destructors in C++ are so much more complicated than all of this.</sarcasm> Seriously, this article could be much longer if it included the really complex issues such as resurrection and the restrictions on finalizers that descend from CriticalFinalizerObject.

I'd like to take a moment to sing some praises of .NET and C#. Although I do disagree with a couple of Microsoft's decisions, on the whole, they've done an excellent job. I'm a huge fan of any language that brings the procedural and functional families together in a more synergetic union, and C# does an excellent job of that. The .NET Framework and runtime have a few rough corners, but overall, they're better than what has come before, and they're obviously the way things are going. So far, I've been pointing out the problems caused by IDisposable, and from this point forward, I'll start looking at solving a couple of these problems.

The fact is, IDisposable is now built-in to .NET languages (though not the runtime), and any solution needs to make use of this interface. We're stuck with it, so to speak, so let's make the best of it.

The (Hopefully) Not-So-Boring Stuff - IDisposable Regulated

The first part of this article discussed the difficulties of IDisposable; this part will look at some "best practices" when writing IDisposable code.

Solving IDisposable's Difficulties - Minimize Use Cases of IDisposable by Utilizing the Disposable Design Principle

One reason for the complexity of Microsoft's recommended IDisposable code pattern is because they try to cover too many use cases. A bit of discipline in the design of IDisposable classes will go a long way:

  • For each unmanaged resource, create exactly one (possibly internal) IDisposable class that is responsible for freeing it. Microsoft followed this principle thoroughly in the BCL implementation. Note that a wrapper type for an unmanaged resource is considered a managed resource.
  • Never derive from an unmanaged resource wrapper type.
  • Create other managed IDisposable types that either own managed resources and/or derive from a type that owns managed resources.
  • Under no circumstances create a type that has to consider both managed and unmanaged resources, when implementing IDisposable. This greatly simplifies the implementation, reducing possible errors.

The Disposable Design Principle is built on these ideas:

  • Level 0 types directly wrap unmanaged resources. These types are generally sealed.
  • Level 1 types are types that derive from Level 1 types and/or contain field members that are Level 0 or Level 1 types.

To expound on this design principle, the small private or internal Level 0 classes that wrap unmanaged resources should be as close to the native API as possible, and should only concern themselves with disposing the resource correctly. All other APIs should be provided in a Level 1 class that has a Level 0 field member. This would result in two loosely-related classes (or class hierarchies): one is only responsible for wrapping the unmanaged resource, and the other only has to refer to a managed resource. This reduces our use cases for IDisposable to only two:

  1. Level 0 types: only deal with unmanaged resources.
  2. Level 1 types: only deal with managed resources (defined by a base type and/or in fields).

Implementing IDisposable on Level 1 types is rather simple: just implement IDisposable.Dispose as calling Dispose on any IDisposable field, and then, if this type is derived from an IDisposable type, call base.Dispose. This is not the place for general shutdown logic. Note the following for this simple implementation:

  • Dispose is safe to be called multiple times because it is safe to call IDisposable.Dispose multiple times, and that's all it does.
  • Level 1 type should not have finalizers; they wouldn't be able to do anything anyway, since managed code cannot be accessed.
  • It is not necessary to call GC.KeepAlive(this) at the end of Dispose. Even though it is possible for the garbage collector to collect this object while Dispose is still running, this is not dangerous since all the resources being disposed are managed, and neither this type nor any derived types have finalizers.
  • Calling GC.SuppressFinalize(this) is likewise unnecessary because neither this type nor any derived types have finalizers.

However, IDisposable is still difficult to implement correctly for the first use case. Due to the complexities of properly implementing IDisposable for unmanaged resources, it's actually best if we don't implement it altogether. This can be accomplished through the diligent use of base types that handle the common logic, or through the use of helper classes that often remove the need for IDisposable.

Solving IDisposable's Difficulties - Helper Classes for Avoiding Implementing IDisposable Directly

It is very common to have to write an unmanaged resource wrapper class for an unmanaged resource that is a pointer to some data structure. For this common use case, a higher-level abstraction is available through Microsoft-provided helper classes. System.Runtime.InteropServices.SafeHandle, System.Runtime.InteropServices.CriticalHandle, and the classes in Microsoft.Win32.SafeHandles allow writing very simple unmanaged resource wrappers if the unmanaged resource may be treated as an IntPtr. However, these are not supported on the .NET Compact Framework; on that platform, I recommend writing your own version of these extremely useful classes.

Level 0 types, in the Disposable Design Principle, should always derive from SafeHandle, if it is available on the target platform. SafeHandle and its derived classes have special P/Invoke support, which helps prevent leaking resources in some rare situations. Interop code should define function parameters, and return types as SafeHandle (or derived types) rather than IntPtr. The CriticalHandle class, in spite of the name, is actually less safe to use than SafeHandle, and should generally be avoided.

The relationship between SafeWaitHandle and WaitHandle is a perfect example of the Disposable Design Principle: SafeWaitHandle is the Level 0 class, and WaitHandle is the Level 1 class that provides the normal end-user API. SafeWaitHandle is in the SafeHandle hierarchy, implementing SafeHandle.ReleaseHandle as a call to the Win32 CloseHandle function; it only concerns itself with how to free the resource. The Level 1 WaitHandle class, in contrast, is not in the SafeHandle hierarchy; and its hierarchy exposes a full API for waitable handles, such as WaitOne.

This means there are four possibilities when having to write a new unmanaged resource wrapper (in the order of ease of implementation):

  1. There is already a Level 0 type for the unmanaged resource. In other words, the unmanaged resource is a pointer type that is already covered by a class derived from SafeHandle. Microsoft has supplied several classes already, including SafeFileHandle, SafePipeHandle, and SafeWaitHandle, among others. In this case, the programmer only needs to create a new Level 1 type.
  2. The unmanaged resource is a pointer type, but doesn't have a suitable Level 0 type already defined. In this case, the programmer needs to create two classes, one Level 0 and one Level 1.
  3. The unmanaged resource that needs wrapping is a simple pointer type along with some additional information (such as a secondary pointer or integral "context" value). In this case, the programmer must also create two classes, but the implementation details of the Level 0 type are more complex.
  4. The unmanaged resource is not a pointer type at all. In this case, the programmer must create two classes, and the implementation details of both are much more complex.

Note that when creating hierarchies of Level 1 types, it is common practice to declare a protected property in the (possibly abstract) base Level 1 type, and this field should have the type and name of the related Level 0 type. For example, the Level 1 abstract base type WaitHandle establishes the Level 1 hierarchy for waitable handles, and it has a protected property named SafeWaitHandle of type SafeWaitHandle.

Wrapping Unmanaged Resources - Using Existing Level 0 Types (The Easy Case)

To define a new Level 1 type that uses a Level 0 type, extend an existing Level 1 hierarchy, if possible.

The example for using existing Level 0 (SafeHandle-derived) types is ManualResetTimer (named to match the existing ManualResetEvent). Of the many timers provided by the .NET framework, they did not include a WaitHandle-based timer that gets signalled when the timer goes off. This "Waitable Timer", as it is called by the SDK, is commonly used by asynchronous programs. For simplicity, this sample does not support periodic timers or timers with asynchronous callback functions.

Note that ManualResetTimer derives from WaitHandle (the Level 1 hierarchy) because the Level 0 SafeWaitHandle already correctly disposes of the unmanaged resource. Because of the Level 0/Level 1 class hierarchy division already in place, implementing ManualResetTimer is quite straightforward.

C#
[SecurityPermission(SecurityAction.LinkDemand, 
   Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("kernel32.dll", EntryPoint = "CreateWaitableTimer", 
        CharSet = CharSet.Auto, BestFitMapping = false,
        ThrowOnUnmappableChar = true, SetLastError = true), 
     SuppressUnmanagedCodeSecurity]
    private static extern SafeWaitHandle DoCreateWaitableTimer(IntPtr lpTimerAttributes,
        [MarshalAs(UnmanagedType.Bool)] bool bManualReset, string lpTimerName);
    internal static SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, 
             bool bManualReset, string lpTimerName)
    {
        SafeWaitHandle ret = DoCreateWaitableTimer(lpTimerAttributes, 
                             bManualReset, lpTimerName);
        if (ret.IsInvalid)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        return ret;
    }

    [DllImport("kernel32.dll", EntryPoint = "CancelWaitableTimer", 
               SetLastError = true), 
     SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DoCancelWaitableTimer(SafeWaitHandle hTimer);
    internal static void CancelWaitableTimer(SafeWaitHandle hTimer)
    {
        if (!DoCancelWaitableTimer(hTimer))
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    }

    [DllImport("kernel32.dll", EntryPoint = "SetWaitableTimer", 
               SetLastError = true), SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DoSetWaitableTimer(SafeWaitHandle hTimer, 
            [In] ref long pDueTime, int lPeriod,
            IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, 
            [MarshalAs(UnmanagedType.Bool)] bool fResume);
    internal static void SetWaitableTimer(SafeWaitHandle hTimer, long pDueTime, 
             int lPeriod, IntPtr pfnCompletionRoutine,
             IntPtr lpArgToCompletionRoutine, bool fResume)
    {
        if (!DoSetWaitableTimer(hTimer, ref pDueTime, lPeriod, 
                 pfnCompletionRoutine, lpArgToCompletionRoutine, fResume))
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    }
}

/// <summary>
/// A manual-reset, non-periodic, waitable timer.
/// </summary>
public sealed class ManualResetTimer : WaitHandle
{
    /// <summary>
    /// Creates a new <see cref="ManualResetTimer"/>.
    /// </summary>
    public ManualResetTimer()
    {
        SafeWaitHandle = 
          NativeMethods.CreateWaitableTimer(IntPtr.Zero, true, null);
    }

    /// <summary>
    /// Cancels the timer. This does not change the signalled state.
    /// </summary>
    public void Cancel()
    {
        NativeMethods.CancelWaitableTimer(SafeWaitHandle);
    }

    /// <summary>
    /// Sets the timer to signal at the specified time,
    /// which may be an absolute time or a relative (negative) time.
    /// </summary>
    /// <param name="dueTime">The time, interpreted
    /// as a <see cref="FILETIME"/> value</param>
    private void Set(long dueTime)
    {
        NativeMethods.SetWaitableTimer(SafeWaitHandle, dueTime, 0, 
                                       IntPtr.Zero, IntPtr.Zero, false);
    }

    /// <summary>
    /// Sets the timer to signal at the specified time. Resets the signalled state.
    /// </summary>
    /// <param name="when">The time that this
    /// timer should become signaled.</param>
    public void Set(DateTime when) { Set(when.ToFileTimeUtc()); }

    /// <summary>
    /// Sets the timer to signal after a time span. Resets the signaled state.
    /// </summary>
    /// <param name="when">The time span after
    /// which the timer will become signaled.</param>
    public void Set(TimeSpan when) { Set(-when.Ticks); }
}

Note the following:

  • Always use SafeHandle or derived types as parameters and return values for interop functions. For example, this sample code uses SafeWaitHandle instead of IntPtr. This prevents resource leaks if a thread is unexpectedly aborted.
  • Since a Level 1 hierarchy is already in place, ManualResetTimer doesn't have to deal with disposing, even of its managed resources. This is all handled by the WaitHandle base type.

Wrapping Unmanaged Resources - Defining Level 0 Types for Pointers (The Intermediate Case)

There are many cases where a suitable Level 0 type doesn't exist. These situations require defining a Level 0 type and then defining a Level 1 type (or type hierarchy). Defining Level 0 types is more complicated than defining Level 1 types.

The example for defining simple Level 0 types is a window station object. This is one of the many resources that is represented by a single IntPtr handle. First, the Level 0 type must be defined:

C#
[SecurityPermission(SecurityAction.LinkDemand, 
   Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("user32.dll", EntryPoint = "CloseWindowStation", 
      SetLastError = true), SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool CloseWindowStation(IntPtr hWinSta);
}

/// <summary>
/// Level 0 type for window station handles.
/// </summary>
public sealed class SafeWindowStationHandle : SafeHandle
{
    public SafeWindowStationHandle() : base(IntPtr.Zero, true) { }
    public override bool IsInvalid
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        get { return (handle == IntPtr.Zero); }
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [PrePrepareMethod]
    protected override bool ReleaseHandle()
    {
        return NativeMethods.CloseWindowStation(handle);
    }
}

Notes on the code:

  • The unmanaged resource deallocation function (in this case, NativeMethods.CloseWindowStation) does take a regular IntPtr (not a SafeWindowStationHandle) to deallocate the resource.
  • Since SafeHandle derives from CriticalFinalizerObject, both IsInvalid and ReleaseHandle may be run in a Constrained Execution Region, meaning:
    • They cannot allocate objects, box values, acquire locks, or call methods through delegates, function pointers, or Reflection.
    • They should be decorated with a ReliabilityContractAttribute and a PrePrepareMethodAttribute.
  • Both IsInvalid and ReleaseHandle may be run from a finalizer during system shutdown, so they may not access any managed objects whatsoever.

Since a Level 0 type's ReleaseHandle only P/Invokes its resource cleanup function and returns, the Constrained Execution Region and finalizer restraints are not troublesome in practice. The only awkwardness is in the additional attributes that are necessary.

Once the Level 0 type is completed, then the Level 1 type may be defined:

C#
[SecurityPermission(SecurityAction.LinkDemand, 
      Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("user32.dll", EntryPoint = "OpenWindowStation", 
        CharSet = CharSet.Auto, BestFitMapping = false,
        ThrowOnUnmappableChar = true, SetLastError = true), 
     SuppressUnmanagedCodeSecurity]
    private static extern SafeWindowStationHandle 
            DoOpenWindowStation(string lpszWinSta,
            [MarshalAs(UnmanagedType.Bool)] bool fInherit, 
            uint dwDesiredAccess);
    internal static SafeWindowStationHandle 
             OpenWindowStation(string lpszWinSta, 
             bool fInherit, uint dwDesiredAccess)
    {
        SafeWindowStationHandle ret = 
          DoOpenWindowStation(lpszWinSta, fInherit, dwDesiredAccess);
        if (ret.IsInvalid)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        return ret;
    }

    [DllImport("user32.dll", EntryPoint = "SetProcessWindowStation", 
         SetLastError = true), SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool 
            DoSetProcessWindowStation(SafeWindowStationHandle hWinSta);
    internal static void SetProcessWindowStation(SafeWindowStationHandle hWinSta)
    {
        if (!DoSetProcessWindowStation(hWinSta))
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    }
}

/// <summary>
/// A window station.
/// </summary>
public sealed class WindowStation : IDisposable
{
    /// <summary>
    /// The underlying window station handle.
    /// </summary>
    private SafeWindowStationHandle SafeWindowStationHandle;

    /// <summary>
    /// Implementation of IDisposable: closes the underlying window station handle.
    /// </summary>
    public void Dispose()
    {
        SafeWindowStationHandle.Dispose();
    }

    /// <summary>
    /// Opens an existing window station.
    /// </summary>
    public WindowStation(string name)
    {
        // ("0x37F" is WINSTA_ALL_ACCESS)
        SafeWindowStationHandle = NativeMethods.OpenWindowStation(name, false, 0x37F);
    }

    /// <summary>
    /// Sets this window station as the active one for this process.
    /// </summary>
    public void SetAsActive()
    {
        NativeMethods.SetProcessWindowStation(SafeWindowStationHandle);
    }
}

Notes:

  • The unmanaged native methods now all use SafeWindowStationHandle for their return values and arguments, rather than IntPtr. Only the resource deallocation function is passed an IntPtr.
  • For simplicity, NativeMethods.OpenWindowStation takes a uint as its desired access mask, rather than a proper enumeration. A real enumeration should be used in production code.
  • The implementation of IDisposable.Dispose is straightforward: dispose of the underlying handle.
  • A finalizer is not necessary because SafeWindowStationHandle has its own finalizer (inherited from SafeHandle) which will dispose of the underlying handle.

Since the window station is a simple example, there is only one Level 1 class rather than a hierarchy of Level 1 classes. To define a hierarchy, the following code pattern should be used:

C#
/// <summary>
/// A base class for window station types.
/// </summary>
public abstract class WindowStationBase : IDisposable
{
    /// <summary>
    /// The underlying window station handle.
    /// </summary>
    protected SafeWindowStationHandle SafeWindowStationHandle { get; set; }

    /// <summary>
    /// Implementation of IDisposable: closes the underlying window station handle.
    /// </summary>
    public void Dispose()
    {
        DisposeManagedResources();
    }

    /// <summary>
    /// Disposes managed resources in this class and derived classes.
    /// When overriding this in a derived class,
    /// be sure to call base.DisposeManagedResources()
    /// </summary>
    protected virtual void DisposeManagedResources()
    {
        SafeWindowStationHandle.Dispose();
    }
}

/// <summary>
/// A window station.
/// </summary>
public sealed class WindowStation : WindowStationBase
{
    /// <summary>
    /// Opens an existing window station.
    /// </summary>
    public WindowStation(string name)
    {
        // ("0x37F" is WINSTA_ALL_ACCESS)
        SafeWindowStationHandle = 
           NativeMethods.OpenWindowStation(name, false, 0x37F);
    }

    /// <summary>
    /// Sets this window station as the active one for this process.
    /// </summary>
    public void SetAsActive()
    {
        NativeMethods.SetProcessWindowStation(SafeWindowStationHandle);
    }
}

Notes:

  • SafeWindowStationHandle is now a protected property. This should be a set by derived classes, usually in their constructors. Note that this may also be a public property (e.g. Microsoft chose to make WaitHandle.SafeWaitHandle public); however, I believe protected is the better choice.
  • When implementing IDisposable in the base class, I assume the Disposable Design Principle instead of using Microsoft's IDisposable code pattern. As a result:
    • Types derived from WindowStationBase may not directly own unmanaged resources, i.e., they must be Level 1 types. Note that they may own Level 0 types, which may own unmanaged resources; they just can't be Level 0 types themselves.
    • There is no need for WindowStationBase (or any derived type) to have a finalizer. Implementing Microsoft's IDisposable code pattern requires a finalizer.
    • I chose to name the resource disposing function DisposeManagedResources, which is logically equivalent to the Dispose(true) of Microsoft's IDisposable code pattern.

Wrapping Unmanaged Resources - Defining Level 0 Types for Pointers with Context Data (The Advanced Case)

Sometimes an unmanaged API requires additional context information in order to deallocate a resource. This requires a Level 0 type that has some additional information attached to it, and this always requires more complex interop code.

The example for defining advanced Level 0 types is allocating memory in the context of another process. The other process' handle needs to be associated with the allocated memory, and it needs to be passed to the deallocation function. First, the Level 0 type:

C#
[SecurityPermission(SecurityAction.LinkDemand, 
  Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("kernel32.dll", EntryPoint = "VirtualFreeEx", 
      SetLastError = true), SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool VirtualFreeEx(SafeHandle hProcess, 
             IntPtr lpAddress, UIntPtr dwSize, uint dwFreeType);
}

/// <summary>
/// Level 0 type for memory allocated in another process.
/// </summary>
public sealed class SafeRemoteMemoryHandle : SafeHandle
{
    public SafeHandle SafeProcessHandle
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        get;

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        private set;
    }
    private bool ReleaseSafeProcessHandle;

    public SafeRemoteMemoryHandle() : base(IntPtr.Zero, true) { }

    public override bool IsInvalid
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        get { return (handle == IntPtr.Zero); }
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [PrePrepareMethod]
    protected override bool ReleaseHandle()
    {
        // (0x8000 == MEM_RELEASE)
        bool ret = NativeMethods.VirtualFreeEx(SafeProcessHandle, 
                             handle, UIntPtr.Zero, 0x8000);
        if (ReleaseSafeProcessHandle)
            SafeProcessHandle.DangerousRelease();
        return ret;
    }

    /// <summary>
    /// Overwrites the handle value (without releasing it).
    /// This should only be called from functions acting as constructors.
    /// </summary>
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [PrePrepareMethod]
    internal void SetHandle
	(IntPtr handle_, SafeHandle safeProcessHandle, ref bool success)
    {
        handle = handle_;
        SafeProcessHandle = safeProcessHandle;
        SafeProcessHandle.DangerousAddRef(ref ReleaseSafeProcessHandle);
        success = ReleaseSafeProcessHandle;
    }
}

Notes:

  • This is very similar to the Level 0 type defined earlier; only this class also keeps a SafeHandle reference to the remote process, which must be passed to VirtualFreeEx.
  • A Level 0 type may contain a reference to another Level 0 type (in this example, SafeRemoteMemoryHandle has a field of type SafeHandle). However, it must explicitly control the field's reference count, which requires an additional boolean field (ReleaseSafeProcessHandle).
  • The process handle is held as a SafeHandle, not an IntPtr. This is because SafeHandle internally implements reference counting to prevent premature deallocation. This is useful both while being held as a field in SafeRemoteMemoryHandle and being passed to VirtualFreeEx.
  • Since SafeProcessHandle may be accessed during CERs, its accessors need the ReliabilityContract and PrePrepareMethod attributes.
  • There is also an additional method, SafeRemoteMemoryHandle.SetHandle, which is designed to execute within a Constrained Execution Region, so it can atomically set both the remote process handle and the unmanaged handle together.
  • Once again, proper enumerations are skipped for simplicity.
  • Also, a more proper handling of the remote process handle would require defining a SafeProcessHandle, and using that in place of the SafeHandle in this sample. This sample has completely correct behavior, but does not provide full type safety.

The Level 1 type reveals the additional complexity needed for creating SafeRemoteMemoryHandle objects:

C#
[SecurityPermission(SecurityAction.LinkDemand, 
  Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("kernel32.dll", EntryPoint = "VirtualAllocEx", 
               SetLastError = true), SuppressUnmanagedCodeSecurity]
    private static extern IntPtr DoVirtualAllocEx(SafeHandle hProcess, 
            IntPtr lpAddress, UIntPtr dwSize,
            uint flAllocationType, uint flProtect);
    internal static SafeRemoteMemoryHandle VirtualAllocEx(SafeHandle hProcess, 
             IntPtr lpAddress, UIntPtr dwSize,
             uint flAllocationType, uint flProtect)
    {
        SafeRemoteMemoryHandle ret = new SafeRemoteMemoryHandle();
        bool success = false;

        // Atomically get the native handle
        // and assign it into our return object.
        RuntimeHelpers.PrepareConstrainedRegions();
        try { }
        finally
        {
            IntPtr address = DoVirtualAllocEx(hProcess, lpAddress, 
                             dwSize, flAllocationType, flProtect);
            if (address != IntPtr.Zero)
                ret.SetHandle(address, hProcess, ref success);
            if (!success)
                ret.Dispose();
        }

        // Do error handling after the CER
        if (!success)
            throw new Exception("Failed to set handle value");
        if (ret.IsInvalid)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        return ret;
    }

    [DllImport("kernel32.dll", EntryPoint = "WriteProcessMemory", 
               SetLastError = true), SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DoWriteProcessMemory(SafeHandle hProcess, 
            SafeRemoteMemoryHandle lpBaseAddress,
            IntPtr lpBuffer, UIntPtr nSize, out UIntPtr lpNumberOfBytesWritten);
    internal static void WriteProcessMemory(SafeRemoteMemoryHandle RemoteMemory, 
                         IntPtr lpBuffer, UIntPtr nSize)
    {
        UIntPtr NumberOfBytesWritten;
        if (!DoWriteProcessMemory(RemoteMemory.SafeProcessHandle, RemoteMemory, 
                                  lpBuffer, nSize, out NumberOfBytesWritten))
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        if (nSize != NumberOfBytesWritten)
            throw new Exception
		("WriteProcessMemory: Failed to write all bytes requested");
    }
}

/// <summary>
/// Memory allocated in another process.
/// </summary>
public sealed class RemoteMemory : IDisposable
{
    /// <summary>
    /// The underlying remote memory handle.
    /// </summary>
    private SafeRemoteMemoryHandle SafeRemoteMemoryHandle;

    /// <summary>
    /// The associated process handle.
    /// </summary>
    public SafeHandle SafeProcessHandle 
           { get { return SafeRemoteMemoryHandle.SafeProcessHandle; } }

    /// <summary>
    /// Implementation of IDisposable: closes the underlying remote memory handle.
    /// </summary>
    public void Dispose()
    {
        SafeRemoteMemoryHandle.Dispose();
    }

    /// <summary>
    /// Allocates memory from another process.
    /// </summary>
    public RemoteMemory(SafeHandle process, UIntPtr size)
    {
        // ("0x3000" is MEM_COMMIT | MEM_RESERVE)
        // ("0x04" is PAGE_READWRITE)
        SafeRemoteMemoryHandle = 
          NativeMethods.VirtualAllocEx(process, IntPtr.Zero, size, 0x3000, 0x04);
    }

    /// <summary>
    /// Writes to memory in another process.
    /// Note: at least <paramref name="size"/> bytes starting
    /// at <paramref name="buffer"/> must be pinned in memory.
    /// </summary>
    public void Write(IntPtr buffer, UIntPtr size)
    {
        NativeMethods.WriteProcessMemory(SafeRemoteMemoryHandle, buffer, size);
    }
}

Notes:

  • The first thing that should stand out is how much more complicated the allocation function is. NativeMethods.VirtualAllocEx is designed to partially run within an explicit Constrained Execution Region. Specifically:
    • It does all the necessary allocations before the CER. In this example, it only needs to allocate the returned SafeRemoteMemoryHandle object.
    • The call to RuntimeHelpers.PrepareConstrainedRegions followed by the empty try block is the way of declaring the finally block to be an explicit Constrained Execution Region. See MSDN for more details on this method.
    • It performs error checking, including throwing exceptions (which may allocate memory) after the CER.
  • The CER provides atomic execution: It guarantees that the IntPtr returned from the unmanaged VirtualAllocEx is wrapped in a SafeRemoteMemoryHandle object, even in the presence of asynchronous exceptions (e.g., if Thread.Abort is called on a thread in a CER, the CLR will wait until the CER is completed before asynchronously raising the ThreadAbortException).
  • CERs were not necessary for the simpler examples because SafeHandle is treated specially when returned from an unmanaged function: the returned value (actually an IntPtr) is used to construct a new SafeHandle atomically. In other words, the CLR supports this behavior for SafeHandle automatically, but now we have to force the same behavior using CERs.
  • Another important note is that the interop code should continue to reference the Level 0 type (e.g., SafeRemoteMemoryHandle) instead of just an IntPtr; this keeps SafeHandle's reference counting involved. Passing the context data (e.g., SafeHandle or SafeProcessHandle) along with a plain IntPtr would be incorrect.
  • The RemoteMemory Level 1 type does expose the additional context property (as RemoteMemory.SafeProcessHandle). This is not required, but often useful.

Notes on how this example is simplified:

  • For simplicity, this example only provides a single Level 1 class instead of a class hierarchy. See the previous example for an example of the Level 1 hierarchy pattern.
  • Again, the process SafeHandle should really be a SafeProcessHandle, and proper enumerations have been omitted.
  • This sample also does not expose a very user-friendly API; it should include both reading and writing at various offsets, and should accept byte arrays instead of pre-pinned memory.
  • Exceptions of type Exception should not be thrown directly; this should be of a more specific type.

Wrapping Unmanaged Resources - Defining Level 0 Types for Non-Pointer Data (The Hard Case)

There are a handful of unmanaged APIs whose handle types are not pointers. Each of these handle types may either be converted to an IntPtr (if they are smaller or equal to the IntPtr type) or treated as additional context data for a fake IntPtr.

The example for non-pointer Level 0 types is the local atom table. There is no real reason to use this antiquated API in a modern program, but this example will illustrate how to handle APIs of this nature. The ATOM type is an unsigned 16-bit integer, and for illustration purposes, the sample is implemented twice: once widening the ushort to IntPtr, and the other treating the ushort as context data for a fake IntPtr.

First, the Level 0 type for atoms, storing the ushort unmanaged handle value inside the IntPtr SafeHandle.handle field:

C#
[SecurityPermission(SecurityAction.LinkDemand, 
   Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("kernel32.dll", EntryPoint = "DeleteAtom", 
               SetLastError = true), SuppressUnmanagedCodeSecurity]
    internal static extern ushort DeleteAtom(ushort nAtom);
}

/// <summary>
/// Level 0 type for local atoms (casting implementation).
/// </summary>
public sealed class SafeAtomHandle : SafeHandle
{
    /// <summary>
    /// Internal unmanaged handle value, translated to the correct type.
    /// </summary>
    public ushort Handle
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        get
        {
            return unchecked((ushort)(short)handle);
        }

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        internal set
        {
            handle = unchecked((IntPtr)(short)value);
        }
    }

    /// <summary>
    /// Default constructor initializing with an invalid handle value.
    /// </summary>
    public SafeAtomHandle() : base(IntPtr.Zero, true) { }

    /// <summary>
    /// Whether or not the handle is invalid.
    /// </summary>
    public override bool IsInvalid
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        get { return (Handle == 0); }
    }

    /// <summary>
    /// Releases the handle.
    /// </summary>
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [PrePrepareMethod]
    protected override bool ReleaseHandle()
    {
        return (NativeMethods.DeleteAtom(Handle) == 0);
    }
}

The only difference of note is the addition of the Handle property, which provides access to the handle, treating it as a ushort. Note the necessity of the ReliabilityContract and PrePrepareMethod attributes on the property accessors. The IsInvalid and ReleaseHandle implementations use Handle instead of handle for ease of implementation.

The additional complexity comes into play with the interop code used with the Level 1 class:

C#
[SecurityPermission(SecurityAction.LinkDemand, 
   Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("kernel32.dll", EntryPoint = "AddAtom", 
        CharSet = CharSet.Auto, BestFitMapping = false,
        ThrowOnUnmappableChar = true, SetLastError = true), 
     SuppressUnmanagedCodeSecurity]
    private static extern ushort DoAddAtom(string lpString);
    internal static SafeAtomHandle AddAtom(string lpString)
    {
        SafeAtomHandle ret = new SafeAtomHandle();

        // Atomically get the native handle
        // and assign it into our return object.
        RuntimeHelpers.PrepareConstrainedRegions();
        try { }
        finally
        {
            ushort atom = DoAddAtom(lpString);
            if (atom != 0)
                ret.Handle = atom;
        }

        // Do error handling after the CER
        if (ret.IsInvalid)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        return ret;
    }

    [DllImport("kernel32.dll", EntryPoint = "GetAtomName", 
        CharSet = CharSet.Auto, BestFitMapping = false,
        ThrowOnUnmappableChar = true, SetLastError = true), 
     SuppressUnmanagedCodeSecurity]
    private static extern uint DoGetAtomName(ushort nAtom, 
                       StringBuilder lpBuffer, int nSize);
    internal static string GetAtomName(SafeAtomHandle atom)
    {
        // Atom strings have a maximum size of 255 bytes
        StringBuilder sb = new StringBuilder(255);
        uint ret = 0;
        bool success = false;

        // Atomically increment the SafeHandle reference count,
        // call the native function, and decrement the count
        RuntimeHelpers.PrepareConstrainedRegions();
        try { }
        finally
        {
            atom.DangerousAddRef(ref success);
            if (success)
            {
                ret = DoGetAtomName(atom.Handle, sb, 256);
                atom.DangerousRelease();
            }
        }

        // Do error handling after the CER
        if (!success)
            throw new Exception("SafeHandle.DangerousAddRef failed");
        if (ret == 0)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

        sb.Length = (int)ret;
        return sb.ToString();
    }
}

/// <summary>
/// Atom in the local atom table.
/// </summary>
public sealed class LocalAtom : IDisposable
{
    /// <summary>
    /// The underlying atom handle.
    /// </summary>
    private SafeAtomHandle SafeAtomHandle;

    /// <summary>
    /// Implementation of IDisposable: closes the underlying atom handle.
    /// </summary>
    public void Dispose()
    {
        SafeAtomHandle.Dispose();
    }

    /// <summary>
    /// Adds a string to the atom table, setting this local atom to point to it.
    /// </summary>
    public void Add(string name)
    {
        SafeAtomHandle = NativeMethods.AddAtom(name);
    }

    public string Name
    {
        get
        {
            return NativeMethods.GetAtomName(SafeAtomHandle);
        }
    }
}

The primary difference between this example and the last one is the need for CERs in every single interop call. The automatic reference counting from SafeHandle is no longer automatic, so it must be done by hand. Every time the underlying unmanaged handle needs to be passed to an unmanaged function, the example of NativeMethods.GetAtomName should be followed:

  1. Initialize return values (in this case, a return buffer) and any error condition variables.
  2. Use a CER to atomically increment the SafeHandle reference count, call the unmanaged function, and decrement the SafeHandle count. Note that incrementing the SafeHandle reference count may fail, which should abort the call. [Alternatively, the incrementing and unmanaged function call may be placed within the try block, but the decrementing must remain in the finally block.]
  3. Perform all error testing: both the SafeHandle increment as well as the unmanaged function result must be considered. Remember that throwing Exception is not recommended in production code; a more specific type should be selected instead.

The second implementation (using context values instead of casting to/from IntPtr) may be chosen if the casting would be awkward, or if the unmanaged handle type won't fit into a single IntPtr field. It is possible to make the SafeHandle.handle field almost meaningless by only assigning it 0 (for invalid handle values) or -1 (indicating the handle - including the context values - is valid):

C#
/// <summary>
/// Level 0 type for local atoms (context implementation).
/// </summary>
public sealed class SafeAtomHandle : SafeHandle
{
    public ushort Handle
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        get;

        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        private set;
    }

    /// <summary>
    /// Default constructor initializing with an invalid handle value.
    /// </summary>
    public SafeAtomHandle() : base(IntPtr.Zero, true) { }

    /// <summary>
    /// Whether or not the handle is invalid.
    /// </summary>
    public override bool IsInvalid
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        get { return (handle == IntPtr.Zero); }
    }

    /// <summary>
    /// Releases the handle.
    /// </summary>
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [PrePrepareMethod]
    protected override bool ReleaseHandle()
    {
        return (NativeMethods.DeleteAtom(Handle) == 0);
    }

    /// <summary>
    /// Overwrites the handle value (without releasing it).
    /// This should only be called from functions acting as constructors.
    /// </summary>
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [PrePrepareMethod]
    internal void SetHandle(ushort handle_)
    {
        Handle = handle_;
        handle = (IntPtr)(-1);
    }
}

Notes:

  • The Handle property is now a context, stored separately from handle.
  • The IsInvalid property tests the handle, which now only has values of 0 or -1, but the ReleaseHandle method still uses Handle for convenience.
  • The Handle setter has been replaced by the SetHandle method. This is done in the example to reflect the fact that most of the time contexts are used, SetHandle will need to take more than one argument.

The only change necessary in the rest of the example is the change in how the handles are set in the NativeMethods.AddAtom constructor method:

C#
ret.Handle = atom;

should be:

C#
ret.SetHandle(atom);

Remember that in a real-world situation, SetHandle would be taking more than one argument.

Summary

To summarize, prefer using the Disposable Design Principle. The DDP splits up resource management responsibilities into Level 0 types (which handle unmanaged resources), and Level 1 types (which are still small wrapper classes that closely resemble the native API, but only handle managed resources):

  1. Level 0 types directly wrap unmanaged resources, and are only concerned with deallocation of their resource.
    1. Level 0 types are either abstract or sealed.
    2. Level 0 types must be designed to execute completely within an atomic execution region.
      • For Constrained Execution Regions, this means that Level 0 types must derive from SafeHandle (which derives from CriticalFinalizerObject).
      • For finally blocks, this means that Level 0 types must derive from a separately-defined SafeHandle type which implements IDisposable to deallocate the unmanaged resource explicitly (possibly called in the context of a finally block) or from a finalizer.
    3. Constructors for Level 0 types must be called from within an atomic execution region.
      • The special full framework interop handling of SafeHandle return values is considered unmanaged code (and therefore an atomic execution region of the strongest guarantee).
    4. Level 0 types may refer to other Level 0 types, but must increment the count of the referred-to object as long as the reference is needed.
  2. Level 1 types only deal with managed resources.
    1. Level 1 types are generally sealed unless they are defining a base Level 1 type for a Level 1 hierarchy.
    2. Level 1 types derive from Level 1 types or from IDisposable directly; they do not derive from CriticalFinalizerObject or Level 0 types.
    3. Level 1 types may have fields that are Level 0 or Level 1 types.
    4. Level 1 types implement IDisposable.Dispose by calling Dispose on each of its Level 0 and Level 1 fields, and then calling base.Dispose if applicable.
    5. Level 1 types do not have finalizers.
    6. When defining a Level 1 type hierarchy, the abstract root base type should define a protected property with the name and type of the associated Level 0 type.

Using the Disposable Design Principle (instead of Microsoft's IDisposable code pattern) will make software more reliable and easier to use.

References and Further Reading

Afterword

In a future article, I hope to address one further drawback to IDisposable: the lack of support for shutdown logic; and provide a (partial) solution. This was originally intended to be part of this article, but it's already too long. I also hope to look at the SafeHandle alternatives for the .NET Compact Framework, which sadly does not support SafeHandle or Constrained Execution Regions.

I'd like to thank my loving almost-wife Mandy Snell, for patiently proofreading this article. On October 4th, 2008, she will officially become Mandy Cleary. :) I also must state that everything good in my life comes from Jesus Christ; He is the source of all wisdom, and I thank Him for all His gifts. "For God giveth to a man that is good in his sight wisdom, and knowledge, and joy" (Ecc. 2:26).

History

  • 2008-09-27 - Fixed bug in the advanced sample, rewrote the summary of the DDP, and added the reference to Microsoft's rationale to not support reference counting
  • 2008-09-22 - Added the References and History sections
  • 2008-09-21 - Initial publication

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Software Developer (Senior)
United States United States
Stephen Cleary is a Christian, husband, father, and programmer living in Northern Michigan.

Personal home page (including blog): http://www.stephencleary.com/

Comments and Discussions

 
General"I have been Illuminated" Pin
Luc7824-Jan-10 4:09
Luc7824-Jan-10 4:09 
General5 Stars, Hands down :) Pin
Midnight Run14-Jan-10 3:46
Midnight Run14-Jan-10 3:46 
QuestionFializer before Dispose? Pin
Nicolae Mogoreanu10-Nov-09 15:33
Nicolae Mogoreanu10-Nov-09 15:33 
AnswerRe: Fializer before Dispose? Pin
Stephen Cleary10-Nov-09 16:22
Stephen Cleary10-Nov-09 16:22 
GeneralRe: Fializer before Dispose? Pin
Nicolae Mogoreanu10-Nov-09 17:45
Nicolae Mogoreanu10-Nov-09 17:45 
QuestionCOM Interopt Classes Pin
wburchard1-Oct-09 11:15
wburchard1-Oct-09 11:15 
AnswerRe: COM Interopt Classes Pin
Stephen Cleary9-Nov-09 10:13
Stephen Cleary9-Nov-09 10:13 
GeneralRe: COM Interopt Classes Pin
wburchard9-Nov-09 10:22
wburchard9-Nov-09 10:22 
COM does use reference counting, but C# does not. This leads me to think that C# wrappers for COM objects would not automatically benefit from the reference counting performed inside of COM. What I don't understand is whether Microsoft did anything special to address this issue inside the Interop code generated when a COM dll is added as a reference in a C# projecct.
GeneralIDisposable on struct Pin
Boing30005-Dec-08 3:42
Boing30005-Dec-08 3:42 
GeneralRe: IDisposable on struct Pin
Stephen Cleary7-Dec-08 16:10
Stephen Cleary7-Dec-08 16:10 
GeneralRe: IDisposable on struct Pin
Boing30007-Dec-08 22:37
Boing30007-Dec-08 22:37 
GeneralRe: IDisposable on struct Pin
Stephen Cleary8-Dec-08 16:25
Stephen Cleary8-Dec-08 16:25 
GeneralVery nice and very good Pin
AnandChavali27-Oct-08 6:24
AnandChavali27-Oct-08 6:24 
GeneralRe: Very nice and very good Pin
Donsw25-Jan-09 13:26
Donsw25-Jan-09 13:26 
GeneralVery very nice Pin
Sacha Barber14-Oct-08 10:03
Sacha Barber14-Oct-08 10:03 
GeneralRe: Very very nice Pin
Stephen Cleary14-Oct-08 11:33
Stephen Cleary14-Oct-08 11:33 
GeneralRe: Very very nice Pin
Sacha Barber14-Oct-08 21:54
Sacha Barber14-Oct-08 21:54 
GeneralFantastic article! Pin
dudeua4-Oct-08 6:44
dudeua4-Oct-08 6:44 
GeneralWow, thanks Pin
Gregory Gadow1-Oct-08 5:48
Gregory Gadow1-Oct-08 5:48 
GeneralVery, very good Pin
nurhi23-Sep-08 3:44
nurhi23-Sep-08 3:44 
GeneralCongrats Pin
Ilíon23-Sep-08 1:19
Ilíon23-Sep-08 1:19 
GeneralRe: Congrats Pin
Stephen Cleary24-Sep-08 17:41
Stephen Cleary24-Sep-08 17:41 
GeneralFunctional programming Pin
tonyt22-Sep-08 14:42
tonyt22-Sep-08 14:42 
GeneralReality Pin
tonyt22-Sep-08 14:10
tonyt22-Sep-08 14:10 
GeneralRe: Reality Pin
Stephen Cleary22-Sep-08 14:43
Stephen Cleary22-Sep-08 14:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.