by Michael S. Kaplan, published on 2010/10/18 07:01 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2010/10/18/10077187.aspx
The question was (as many of these questions) are, entirely reasonable.
I tried to write a simple program to open a lot of fonts and dispose them. I am not seeing the GDI+ memory itself getting freed. Am I doing something wrong in this code? My code is written in C#, as shown below:
static void Main(string[] args) {
int i = 0;
foreach (FontFamily fontfamily in FontFamily.Families) {
i++;
try {
Font f = new Font(fontfamily, (float)8.0, FontStyle.Bold);
Console.WriteLine("Created Font #{0} {1} ", i, f.Name);
f.Dispose();
f = null;
} catch (Exception ex) {
Console.WriteLine("Exception while creating Font {0} {1}", fontfamily.Name, ex.ToString());
}
// Console.ReadLine();
fontfamily.Dispose();
}
Console.WriteLine("Finished");
Console.ReadLine();
}
My questions are: When exactly does the GDI+ memory that corresponds to a Font get freed? And how can we free the memory? Any suggestions will be sincerely appreciated.
Like I said, quite reasonable!
However, there is no guaranteed symmetry of reasonableness between question and answer. And this case is no exception....
The answer that came back from some of the GDI+ experts was a surprise to some.
It would have been a surprise to me if I weren't so numb to shock in things in GDI+ that didn't work the way I wanted and/or expected them to:
To optimize performance, GDI+ retains some resources throughout the lifetime of the invoking process. These resources are not freed by the call to dispose. The only supported way to free these resources is to end the process.
Ouch.
And then one of my favorite people knowledgeable about details in WinForms that few people know about, Jessica Fosler, added:
There’s some caveats about HFonts as well – if you’ve called ToHFont at all (maybe to pass out to a p/invoke call) you need to call DeleteObject yourself.
(ref Font.ToHfont Method)
But yes, if I remember - if you Delete a font that’s still in use your control will be majorly unhappy and revert back to a really ugly system font. If you’re disposing the font, you need to make sure it’s not in use anywhere still.
Looks like there isn't any way to free that memory up, in that case.
Seems like a[nother] good reason to not use GDI+ if you can skip using it, just in case the myriad of other problems don't manage to dissuade....
Zooba on 18 Oct 2010 10:15 PM:
Surely that's only a good reason to not mis-use it, since keeping a font loaded for the length of a process would fit most uses. In fact, with the hatred of global variables at the level it often is (based on [AU]university/[US]college-level courses I have seen, and completely irrational), the likelihood of code creating and deleting a font each use is probably high enough to justify pretending to delete it.
Mike Dimmick on 19 Oct 2010 8:26 AM:
The commentary in the available source code (thanks to John Robbins for his .NET Mass Downloader) for .NET 2.0 SP1 indicates that GDI+ caches (the native equivalent of) *FontFamily* objects, not Font objects, and that the family objects are ref-counted. Of course fonts themselves could be cached and ref-counted, but I actually can't see a reason to do this - the variety of fonts is too great.
For .NET 4.0 ResourceConsumption and ResourceExposure attributes have been added, but as far as I can tell this is for a static analysis tool to investigate the closure of resource consumption and doesn't necessarily relate to caching inside an unmanaged library. The attributes are conditional, requiring RESOURCE_ANNOTATION_WORK to be defined when compiling the Framework to end up in the compiled assemblies.
If the unmanaged objects live on the default Windows heap, of course, and they're not too large, when they're freed they wind up on the lookaside lists designed to speed allocation and reduce fragmentation. Even if they don't the heap might well not free the segment (let's be honest, here, the program doesn't even exhaust its initial unmanaged heap segment), so the virtual size and probably working set wouldn't shrink. Monitoring the Virtual Bytes performance counter really only reveals gross leakage. Monitoring Working Set is nearly pointless as it depends so much on the pressure from other processes and subsystems in Windows.
My understanding was that HFONTs are reference-counted like other GDI objects, though this may be an implementation detail (and I ran out of brushes on Windows CE after calling DeleteObject on a brush that was still selected in a DC that was itself subsequently deleted, without deselecting the brush!) However, the HFONT you give to a control to draw itself doesn't, typically, remain selected in a DC: the control selects it into the DC that BeginPaint gives it. If the HFONT isn't valid, SelectObject fails and you get the default font for the DC, which is the System font.