LUNY.CO.UK

GUI Development:Windows Development

Resource Management

Sometimes handling unmanaged resources in the .NET environment can be pain. Here is a snippet of code:

ControlHelpers.UpdateFont(
	new HandleRef(this, this.Handle),
	new HandleRef(this.Font, this.Font.ToHfont());

This innocent line of code simply passes on a change of font to some managed C++. However it leaks a GDI handle every time it is used. This is because the statement this.Font.ToHfont() creates a GDI handle every time it is called and relies on the method DeleteObject to be called to release the handle. The problem we had is that the method called in the managed C++ passes on the handle as a pointer into some unmanaged C++. An unusual case scenario but lead to a solution which can be quite useful when dealing with unmanaged resources.

Out solution was to create a class called FontHandleWrapper is a special kind of static storage that takes the new font and stores it as a WeakReference. To describe what a weak reference we can look at the MSDN remark about it:

A weak reference allows the garbage collector to collect an object while still allowing an application to access the object. If you need the object, you can still obtain a strong reference to it and prevent it from being collected. For more information about how to use short and long weak references, see Weak References.

So this means our unmanaged code can use that handle and allow the .NET to keep an eye on it for garbage collection. The clever part about our class is the way the handles are stored. Internally the class has a static dictionary which stores each handle that is created. When a handle is created its unique key is made up of the Font name, height and style. So if the same font asks for another handle, the stored dictionary will give back a handle already available. Additionally when a handle is created/ asked for, the class will attempt to clean up any of the handles that marked as not IsAlive by deleting them from the dictionary.

Lets take a look at the class:


FontHandleWrapper

	using System;
	using System.Text;
	using System.Drawing;
	using System.Globalization;
	using System.Collections.Generic;
	using System.Runtime.InteropServices;
	using System.Diagnostics.CodeAnalysis;
	
	namespace Utils
	{
	    /// 
	    /// A wrapper class to hold a font handle, which also makes sure it
	    /// is destroyed when not required.
	    /// 
	    internal class FontHandleWrapper : IDisposable
	    {
	        [DllImport("gdi32.dll", CharSet = CharSet.Auto)]
	        [return: MarshalAs(UnmanagedType.Bool)]
	        internal static extern bool DeleteObject(IntPtr hObject);
	
	        #region Fields
	        // List of existing references to font handles created.
	        static Dictionary fontHandleList = new Dictionary();
	
	        // Handle to font copy.
	        IntPtr handle;
	        #endregion
	
	        #region Construction / Destruction
	        private FontHandleWrapper(Font font)
	        {
	            this.handle = font.ToHfont();
	        }
	
	        public void Dispose()
	        {
	            this.Dispose(true);
	            GC.SuppressFinalize(this);
	        }
	
	        [SuppressMessage("Microsoft.Performance", "CA1801",
	          Justification = "'disposing' parameter never used, but required for standard Dispose method.")]
	        protected void Dispose(bool disposing)
	        {
	            if (this.handle != IntPtr.Zero)
	            {
	                DeleteObject(this.handle);
	                this.handle = IntPtr.Zero;
	            }
	        }
	
	        ~FontHandleWrapper()
	        {
	            this.Dispose(false);
	        }
	        #endregion
	
	
	        #region Properties
	        internal IntPtr Handle
	        {
	            get
	            {
	                return this.handle;
	            }
	        }
	        #endregion
	
	        #region Dictionary Methods
	        static public FontHandleWrapper Create(Font font)
	        {
	            FontHandleWrapper wrapper = null;
	
	            string key = string.Format(CultureInfo.CurrentCulture,
	                             "{0}~{1}~{2}",
	                             font.Name,
	                             font.Height.ToString(NumberFormatInfo.InvariantInfo),
	                             font.Style.ToString());
	
	            WeakReference value = null;
	
	            if (fontHandleList.TryGetValue(key, out value))
	            {
	                wrapper = value.Target as FontHandleWrapper;
	
	                if (wrapper == null)
	                {
	                    wrapper = new FontHandleWrapper(font);
	                    value.Target = wrapper;
	                }
	            }
	            else
	            {
	                wrapper = new FontHandleWrapper(font);
	                fontHandleList.Add(key, new WeakReference(wrapper));
	            }
	
	            // Clean up dictionary.
	            List keysToDelete = new List();
	
	            foreach (KeyValuePair vp in fontHandleList)
	            {
	                if (vp.Value.IsAlive == false)
	                {
	                    // Any weak reference object which is still null
	                    // gets removed to stop dictionary being full of
	                    // unused references.
	                    keysToDelete.Add(vp.Key);
	                }
	            }
	
	            foreach (string keyStr in keysToDelete)
	            {
	                fontHandleList.Remove(keyStr);
	            }
	
	            return wrapper;
	        }
	        #endregion
	    }
	}