You need some intricate knowledge of how the run-time works. Take for instance
something like (pseudo)
IntPtr i1 = GetHdc();
DoSomethingWithDC(new HandleRef(null, i1), ...);
ReleaseDC(i1);
Okay, that code demonstrates the problem. DoSomethingWithDC can throw an
exception (OOM, SOE,
TAE, or any number of others)... If it does i1 is lost for good, and you can't
release it. Now, if you do this
with a Graphics object, the IntPtr is actually stored INTERNALLY first... The
Graphics object actually
has an internal member variable allowing it to wrap and then return the hDC...
If an exception occurs,
then at some point the GC will kick in and run the finalizer for the Graphics
and collect the resource by
calling ReleaseDC explicitly.
That is what I mean by wrapping the IntPtr in a managed object. For instance,
the CreateCompatibleDC
in the tutorial allocates an hDC, but doesn't wrap it in a managed object. If
the method fails later, there is
no storage of the IntPtr to be cleaned up, it just leaks. If you placed this in
say a new class called HandleDC
then a finalizer could enforce the release of the resource.
public class CompatibleDC {
private IntPtr dc = IntPtr.Zero;
~CompatibleDC() { Release(); }
private CompatibleDC() {}
public static CompatibleDC CreateCompatibleDC(IntPtr initialDC) {
CompatibleDC cDC = new CompatibleDC(); // allocate memory here, if OOM,
then no leakage.
cDC.dc = Win32.CreateCompatibleDC(initialDC); // Wrapped in an object
instance now.
return cDC; // Give this to the client
}
// Not thread-safe
public IntPtr DC { get { return this.dc; } }
public void Release() {
if ( dc != IntPtr.Zero ) { Win32.ReleaseDC(dc); dc = IntPtr.Zero; }
}
}