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 current bitmap.

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 updated version.