I few years ago I wrote a small utility class named Win32Window, so that I could write
an automated tool to close down some of the dialogs that would appear whenever outlook
lost a server connection (which was often, since it happened whenever I opened or
closed my laptop). It wraps some of the high-level Win32 window handling functions.
As part of that, I wrote a couple of routines to do screen and window capture, though
I've never really used them.
Yesterday I got an email from someone who was using the routine for real, and it was
eating his system's lunch (not actually the term he used, but words to those effects).
It's always unfortunate when a poorly-coded sample makes it out into the wild. One
would hope not to have any of those, but I learned in writing my book that a) It's
not possible to be perfect on technical details, b) that if you cover enough details,
you will make some mistakes, and c) some of them will be howlers, and make people
think you are a total moron.
I was hoping this case wouldn't be that bad.
I opened up my project, modified my test app so that it would capture screenshows
on a timer, started up perfmon to look at the .NET memory counters, and set it running,
with one screenshot ever 100 mS. Though the counters on perfmon barely budged, in
about 15 seconds things were getting slow, and soon after that, things got *really*
slow - so slow, that I had to reboot.
In looking at my code, I found that I hadn't released some of the unmanaged graphics
items, but fixing that issue didn't change the behavior at all. I switched to watching
the normal (ie not .net) memory counters, and found that they went up a big chunk
every time I did a screenshot.
I played around with the excelled CLR Profiler, but the current problem with the CLR
profiler is that it doesn't have a very good delta view, and it's optimized for looking
only at managed objects, not managed wrappers, which tend to be small in the managed
world but big in the unmanaged world (think of a bitmap. On the managed size, there
is a single IntPtr reference to the unmanaged object, which might easily exceed a
megabyte in size). So, I didn't get anywhere with it.
I next added a "CLR" button in my app, to call:
If my hunch was correct, this would find that managed object and finalize it, and
the memory use would go down. That proved to be correct, which led me on a hunt further.
The final issue was actually two issues. First, I wasn't freeing a Graphics object
that I should have. Second, I had code that was something like:
desktopWindow.Image = Win32Window.DesktopAsBitmap;
which works fine, but has the side effect of never disposing the
But I thought managed code meant I didn't have to think about such things...
Unfortunately, this is a case where our current solution doesn't work very well. In
an ideal world, the GC would have cleaned up after those huge bitmaps, but unfortunately,
it has no way of knowing how expensive the underlying unmanaged objects really are,
so I could allocate tons of new Images with creating enough memory pressure to force
a GC to occur. That's somewhat unfortunate.
The fix for the second issue is to call dispose on the old image. That gets me back
to a steady-steate situation. The code will make its way up to the GDN archive in
the next few days; if you're using this code, drop me a line and I'll send you an