About two weeks ago I started working on the “Print All Fonts In Path” feature in Printer’s Apprentice. This feature print catalogs and sample sheet for all fonts in a selected folder tree. And the fonts are typically not currently installed in Windows. (note: Previous versions could print catalogs. But v8 will print both catalogs and sample sheets.)
Once I got the whole process working start to finish, I started to get these errors when the Print dialog box was closed.
The image on the left is the error as it occurs in Visual Studio 2005 (VS). And on the right is what happens when the compiled executable runs by itself. The exception detail follows below.
System.AccessViolationException was unhandled
Message=”Attempted to read or write protected memory. This is often an indication that other memory is corrupt.”
at System.Drawing.SafeNativeMethods.Gdip.IntGdipDeleteFontFamily(HandleRef fontFamily)
at System.Drawing.SafeNativeMethods.Gdip.GdipDeleteFontFamily(HandleRef fontFamily)
at System.Drawing.FontFamily.Dispose(Boolean disposing)
How nice. This bug became the problem of the week.
The first place to start is an understanding of the GDI+ and PrivateFontCollection (PFC) objects. Applications based on .NET use GDI+ for drawing and printing. GDI+ is the successor to the existing Win32 GDI subsystem. It makes programming complex text and graphic layouts much easier and gives us print preview objects built right into the platform. GDI+ also includes it own font management functions and you need to use them if you use the GDI+ PrintDocument and PrintPreview controls. (GDI+ will not render text in a font loaded with the Win32 AddFontResource() function.)
To draw a string using an uninstalled font, you first load the font into a PrivateFontCollection. This class, from system.drawing.text namespace, is a wrapper around an object of the same name in Microsoft’s GDI+ library. It allows the developer to use uninstalled fonts in an application. For example, a developer might ship a proprietary bar code font along with his mailing label application.
Printer’s Apprentice uses a globally scoped PFC to manage fonts on the printed page. The Print All Fonts in Path function goes through these steps to generate a catalog:
- Walk the directory path to generate a list of TrueType and OpenType fonts.
- Add each font to a globally scoped PFC – oPFC.AddFontFile(pFileName).
- Show the Print Preview window. Text functions create and use fonts from the oPFC object. A PrintDocument object does the heavy lifting and draws the layout on a PrintPreview control or printer device.
- Once the dialog closes, the PFC is disposed.
- Boom – the app crashes.
It was easy to narrow the error to the lines below. The stack trace clearly points to disposing the PrivateFontCollection. What was strange is that VS did not halt on the actual line. It would fail a few moments after the line was executed.
oPFC.Dispose()oPFC = Nothing
But the PrivateFontCollection.Dispose() call gave me some ammunition to search with. And I was eventually lead to MS Knowledge Base article 901026.
Memory corruption or an access violation may occur in a custom application that uses the PrivateFontCollection object in Windows XP
What is interesting about the article is that it states that the corruption occurs when the application has outstanding references to GpFontFamily objects. To me, this means the code loads a font to a PFC, creates a Font object, renders some text, then disposes the PFC without disposing the Font object. I then spent a lot of time combing through code trying to find Font objects that were not properly disposed. I even loaded up SciTech’s .NET Memory Profiler looking for leaks. No luck. The app still crashes.
I then built a demonstration application in an attempt to duplicate the problem in a very simple environment. PFCTest.exe simply reads a directory of TTFs, adds the fonts to a PFC and then disposes the fonts. The fonts are not used at all in the application. It runs and crashes as expected under Windows XP. With Vista, it runs fine. I was able to read the fonts and dispose the PFC over 100 times. Look at the execution count label in the screenshots below.
Here is the Visual Studio 2005 project: pfctest.zip
My next step was to obtain and install the 901026 hotfix. Since it is a hotfix and not part of a regular Windows XP update, I had to request it from Microsoft. They got the fix to me rather quickly. But I could not install it.
What next? I need to get in touch with Microsoft again about this and see if I can get a setup package that will install. The package they sent appears to be for Windows Server 2003.
But a larger issue looms. This is clearly a bug in the GDI+ subsystem. When will it get fixed? Will a fix be included in XP Service Pack 3? And it is not the first bug I have run into… If I take out the oPFC.Dispose() code, then Printer’s Apprentice will have a slow memory leak. Until I get a resolution from Microsoft, it will have to be my solution for now as I have to continue working on other features. Yuck. — Bryan