How to make sure Icon resources are getting freed?

T

Terry

After some playing around with icons and ImageList, I'm not quite
sure I understand what's happening. Either the resources are not really
getting cleaned up like I thought they would or what I'm using to measure
the resources isn't accurate, or there's something else going on.

I'm using Task Manager and added the column for "GDI Objects". I created a
little test Form and added some code to load an icon in various ways, and
then call Dispose() or DestroyIcon(). I performed three tests.

1. Load an icon using "ExtractIcon()" (the IconLoader.extractIcon below is
just a
static method I wrote that does the actual ExtractIcon and then creates an
"Icon" object using FromHandle() and returns the icon)

Icon icon = IconLoader.extractIcon(@"F:\Media\Icons\go.ico", 0);
icon.Dispose();

What happens is on the "extractIcon()" call I see the GDI handle count go up
by 2. The GDI handle count remains the same after the "Dispose()".

2. Load an icon using "ExtractIcon()" but use "DestroyIcon()" to clean up
the icon resource

Icon icon = IconLoader.extractIcon(@"F:\Media\Icons\go.ico", 0);
IconLoader.destroyIcon(icon.Handle);

What happens here, is on the "extractIcon()" call the GDI handle count goes
up by 2 and on the "destroyIcon()" call, it goes down by 2.

3. Load an icon from an embedded resource and call "Dispose()"

Stream s =
this.GetType().Assembly.GetManifestResourceStream("TestResourceUsage.go.ico"
);
Icon icon = new Icon( s );
icon.Dispose();

What happens here is on the "new Icon(s)" line, the GDI handle count goes up
by 2 and on the "Dispose()" call it goes down by 2.

The way I'm reading the results of these three tests is that if you load a
resource using a Win32 API call like "ExtractIcon()" then you *must* call
"DestroyIcon()" to free the resource. If it's loaded from a managed
assembly then it's ok for you to just call "Dispose()" to clean up the
resource.

I'm still confused when it comes to ImageLists. When I watch the GDI Handle
count as I'm adding an image or icon to an ImageList, I see the GDI handle
count go up by 2 (which corresponds to what Nicholas had said about the
ImageList making a copy of the resource) but no matter what I do, I'm unable
to recover the resources that are allocated by the ImageList. Even if I
clear the Images collection, call "Dispose()" on the ImageList and set it's
reference to "null" the allocated GDI handles are never recovered - even if
the image is loaded from the managed manifest. I would expect that clearing
the ImageList would also call Dispose() on each of the images contained in
the ImageList (but maybe it doesn't).

Maybe what I'm doing is not a valid test, but it makes me uncomfortable when
I get behavior that doesn't seem consistent. Are my tests invalid? My
conclusions wrong? Or is there something else going on here that I'm
unaware of?

Thanks,
Terry
 
M

Mattias Sjögren

Terry,
The way I'm reading the results of these three tests is that if you load a
resource using a Win32 API call like "ExtractIcon()" then you *must* call
"DestroyIcon()" to free the resource.

Correct, the icon object doesn't take ownership of the handle when you
use Icon.FromHandle(), so you're still responsible for freeing it.

But you can't just immediately DestroyIcon the handle either, since
the handle is used internally by the Icon object. You should only call
DestroyIcon after the Icon object has been disposed or GCd, which you
may not know when it happens. So my solution to this in the past has
been

Icon tmp = Icon.FromHandle(myHandle);
Icon icon = (Icon)tmp.Clone();
tmp.Dispose();
DestroyIcon(myHandle);

I would expect that clearing
the ImageList would also call Dispose() on each of the images contained in
the ImageList (but maybe it doesn't).

No, I don't think it does. If you keep references to the images you
add to the ImageList, try to manually Dispose them as well after you
have disposed the ImageList, and see if that makes the handle count go
down.



Mattias
 
T

Terry

Mattias Sjögren said:
Terry,


Correct, the icon object doesn't take ownership of the handle when you
use Icon.FromHandle(), so you're still responsible for freeing it.

But you can't just immediately DestroyIcon the handle either, since
the handle is used internally by the Icon object. You should only call
DestroyIcon after the Icon object has been disposed or GCd, which you
may not know when it happens. So my solution to this in the past has
been

Icon tmp = Icon.FromHandle(myHandle);
Icon icon = (Icon)tmp.Clone();
tmp.Dispose();
DestroyIcon(myHandle);

That's pretty much what I'm doing, but I'm letting the ImageList make the
clone. From what I've read about ImageList, the "add()" method makes a copy
of the object being passed in. (This explains why the GDI handle count goes
up when I add it to the image list and is also the reason why "IndexOf()" is
not supported in ImageCollection.) So, after I add the image to the
ImageList I call "DestroyHandle()" on it and the GDI handles are reclaimed
at that point (according to Task Manager).
No, I don't think it does. If you keep references to the images you
add to the ImageList, try to manually Dispose them as well after you
have disposed the ImageList, and see if that makes the handle count go
down.

But, if the ImageList does in fact make a *copy* of the Image being passed
in this wouldn't really do anything, would it? Or is it different for
resources loaded via ExtractIcon? i.e. the actual resource is reference
counted?

I just want to write a program that doesn't leak resources. I have to say
that, in this case any way, the benefits of .NET have added a layer of
confusion to managing resources.

Thanks,
Terry
 
M

Mattias Sjögren

That's pretty much what I'm doing, but I'm letting the ImageList make the
clone. From what I've read about ImageList, the "add()" method makes a copy
of the object being passed in. (This explains why the GDI handle count goes
up when I add it to the image list and is also the reason why "IndexOf()" is
not supported in ImageCollection.) So, after I add the image to the
ImageList I call "DestroyHandle()" on it and the GDI handles are reclaimed
at that point (according to Task Manager).

Yes the ImageList puts a copy of the image in its internal bitmap (the
native Comctl ImageList that it wraps does this), but I also think it
keeps a reference to the original object around.



Mattias
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top