« Always wrap GZipStream with BufferedStream | Main | Building Code at Logos: Repository Layout »

05 November 2012

How to Crash Many WPF Applications (WPF 4 Edition)

This is a follow-up to my three-year-old post: How to Crash every WPF Application.

Now that we’ve released Logos 5, which updates the version of WPF we target from WPF 3.5 to WPF 4.5, our customers are really helping us stress-test WPF 4.x. (It’s now a little sad that after three years, ours is the first (and only!) WPF 4 application installed on many of our users’ systems.)

On some systems, the application was crashing at startup with the following exception:

System.ArgumentException: An item with the same key has already been added.
  at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
  at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
  at MS.Internal.FontFace.PhysicalFontFamily.ConvertDictionary(IDictionary`2 dictionary)
  at MS.Internal.FontFace.PhysicalFontFamily.MS.Internal.FontFace.IFontFamily.get_Names()
  at System.Windows.Media.FontFamily.get_FamilyNames()
  at Libronix.Utility.Windows.FontFamilyUtility.<>c__DisplayClass2.<FindSystemFontByTitle>b__0(FontFamily font)
  at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
  at Libronix.Utility.Windows.FontFamilyUtility.FindSystemFontByTitle(String strTitle)
  at LDLS4.AppModel.CreateMainWindow()
  at LDLS4.OurApp.StartupCompleted()

We traced this to having a font with multiple family names in the same culture (or two cultures that WPF considers equivalent; in this particular case, es-ES and es-ES_tradnl were considered identical). PhysicalFontFamily.ConvertDictionary tries to build a dictionary mapping cultures to names, but assumes the cultures from the font are all unique, so inserting a duplicate value crashes.

The result of this bug is that any WPF 4 application that tries to enumerate the system fonts and get their names will crash. The most common font that causes this bug appears to be "DiagramTTBlindAll", a symbol font installed with ChessBase 11.

Although new to us, this is not a new bug. It was reported against Expression Blend in March 2010, and identified as a WPF bug. In February 2012, it was re-reported against the WPF designer in Visual Studio 2010. At that point a Connect issue already existed (but is unavailable now).

Microsoft, we love WPF, but the fact that simple bugs go unfixed for 30 months is frustrating. I know that open sourcing WPF would be much more complex than open sourcing ASP.NET (due to the unmanaged milcore portion and the deployment considerations), but if you put WPF on CodePlex, I'll submit a pull request with a fix the very next day. :-)

Meanwhile, if you're a WPF 4.5 developer, here's a workaround for the crash. Use FontUtility.GetSystemFontFamilies() instead of Fonts.SystemFontFamilies if you will be accessing the names of the returned fonts.

/// <summary>
/// Provides utility methods for fonts.
/// </summary>
public static class FontUtility
{
    /// <summary>
    /// Gets the system font families.
    /// </summary>
    /// <returns>A collection of system font families.</returns>
    public static ReadOnlyCollection<FontFamily> GetSystemFontFamilies()
    {
        List<FontFamily> systemFontFamilies = new List<FontFamily>();
        foreach (FontFamily fontFamily in Fonts.SystemFontFamilies)
        {
            try
            {
                // trigger the exception
                var unused = fontFamily.FamilyNames;

                // add the font if it didn't throw
                systemFontFamilies.Add(fontFamily);
            }
            catch (ArgumentException)
            {
                // certain fonts cause WPF 4 to throw an exception when the FamilyNames property is accessed; ignore them
            }
        }

        return systemFontFamilies.AsReadOnly();
    }
}

Posted by Bradley Grainger at November 05, 2012 10:24 AM