When will the static control automatically delete the image loaded into it, and when is it the responsibility of the application?


If you create a static control with initial contents (for example, by creating a BITMAP or ICON control in a dialog template), then the static control will load the contents upon creation and destroy the contents upon destruction. So at least in the case where you don't touch the static control, things will work automatically.

But once you touch it, things get confusing.

If you send the STM_SET­IMAGE message to a static control, this does a few things (assuming your parameters are all valid):

  • The previous image is replaced by the new image you passed.
  • The message returns a handle to the previous image.
  • The static control turns off automatic image deletion.

The third part is the tricky part. If you ever (successfully) send a static control the STM_SET­IMAGE message, then it says, "Okay, it's all your problem now." You are now responsible not only for destroying the new image, but you are also responsible for destroying the old image that was returned.

In other words, the following operation is not a nop:

HBITMAP hbmPrev = SendMessage(hwndStatic, STM_SETIMAGE,
                              IMAGE_BITMAP, (LPARAM)hbmNew);
SendMessage(hwndStatic, STM_SETIMAGE,
            IMAGE_BITMAP, (LPARAM)hbmPrev);

This sounds like a nop, since all you did was change the image, and then change it back. But the side effect is also that you made the static control go into your problem mode, and the original image will no longer be automatically destroyed. If you forget to destroy it yourself, then you have a leak.

Wait, it gets worse.

If you are using version 6 of the common controls, then things get even more confusing if you use the STM_SET­IMAGE message to change the IMAGE_BITMAP of a SS_BITMAP static control, and the bitmap you pass is a 32-bpp bitmap, and the image has a nonzero alpha channel, then the static control will make a copy of the bitmap you passed in and act as if you had passed that copy instead.¹ This by itself is no big deal, because the responsibility for destroying the image you passed in still resides with you, the application, so the rules haven't changed there.

The nasty bit is that the application also must assume responsibility for destroying the secret copy. That bitmap you didn't even know existed and don't have a handle to? Yeah, you're on the hook for that one too.

How unfair.

Even more confusing is that if you send STM_SET­IMAGE a second time, it will replace the bitmap and return a handle to the secret copy (which is a bitmap you've never seen before).

This means that the following assertion can fire:

HBITMAP hbmPrev = SendMessage(hwndStatic, STM_SETIMAGE,
                              IMAGE_BITMAP, (LPARAM)hbmNew);
HBITMAP hbmBack = SendMessage(hwndStatic, STM_SETIMAGE,
                              IMAGE_BITMAP, (LPARAM)hbmPrev);
assert(hbmNew == hbmBack); // ??

You would think that the assertion is safe because all you did was change the bitmap to hbmNew, then change it back. And when you change it back, the "previous value" is the value hbmNew you set it to on the previous line.

Except that if hbmNew satisfies the above magic criteria, then the value in hbmBack is not hbmNew but rather the handle to the secret copy.

Which you have to remember to destroy.

Yuck.

The secret copy is not too secret. You can get a handle to it by sending the STM_GET­IMAGE message. Which you now need to do when you destroy the static control, just in case it's the secret copy. You need to compare the current image against the one that you thought you passed in, and if they are different, then you have the secret copy that needs to be destroyed as an extra step.

Yes, this sucks. I apologize.

(My recommendation: To detect whether a "secret copy" occurred, do a STM_GET­IMAGE after your STM_SET­IMAGE and see if the handles match.)

¹ The secret copy is not an exact copy. (After all, if it were an exact copy, then there would be no need to create the copy. It could just use the handle you passed in.) Instead, the secret copy is a copy of the original, followed by some additional munging so that it can be displayed on the screen while respecting the alpha channel you passed in.

Comments (25)
  1. Joshua says:

    Is there a fixed version of this control that doesn't require the calling code to free the secret copy somewhere?

    [How do you fix this without introducing a double-free bug in code written to the old model? -Raymond]
  2. OneDeveloper says:

    Keep the apology, send fixed binaries.

  3. Sally says:

    Raymond, does any of this apply to STM_SETICON? (You only mention STM_SETIMAGE.)

  4. Joshua says:

    [How do you fix this without introducing a double-free bug in code written to the old model? -Raymond]

    Class STATIC2?

    [We tried that before. It didn't work. -Raymond]
  5. Joker_vD says:

    That really sucks. Like those dynamic libraries that insist on you personally deallocating TLS before a thread exits.

  6. VZ says:

    Thanks Raymond, it's great to have a confirmation that this is indeed how it works because even though we figured this out ourselves (or at least I think I think we did… I'll need to review the code github.com/…/statbmp.cpp in light of this post), I just couldn't believe this behaviour could have been intentional. I hope it's at least not going to change in the future versions if it is.

    [I added a recommendation at the end of the article to detect whether "secret copying" occurred. Hopefully that will be a bit more future-proof in case the behavior changes. -Raymond]
  7. Joshua says:

    [We tried that before. It didn't work. -Raymond]

    This should work for exactly the same reason it didn't work in the other article. True, the shell won't be able to use it, but just about everybody else can. There's little point for accessibility software looking for image controls anyway.

    [It's not accessibility software. It's random apps that snoop around trying to mess with other windows. -Raymond]
  8. Joshua says:

    [It's random apps that snoop around trying to mess with other windows. -Raymond]

    We may not know what windows they mess with, but we know which ones they don't: any application or dialog box not yet written.

  9. SimonRev says:

    Hmm,  I think that MSDN documentation needs updating.  It states that in version 6 of the common controls a copy is never made (i.e. calling STM_SETIMAGE always returns the handle from the previous call).

    Of course, in the next paragraph it talks about XP might make copy.

    In the end it seems like the easiest behavior (for consumers of the control) would be to treat the copy as an implementation detail.  STM_GETIMAGE and STM_SETIMAGE would return the original bitmap handle.  The copy would always get freed on the next STM_SETIMAGE call or when the window was destroyed.

  10. GregM says:

    "In the end it seems like the easiest behavior (for consumers of the control) would be to treat the copy as an implementation detail.  STM_GETIMAGE and STM_SETIMAGE would return the original bitmap handle.  The copy would always get freed on the next STM_SETIMAGE call or when the window was destroyed."

    That would result in a memory leak if you passed in one of those bitmaps that results in a secret copy.  You need to do a GETIMAGE immediately after SETIMAGE, and if the handle is different, it made a copy, so delete the one you sent in.

  11. JonPotter says:

    So if you pass in an image with an alpha channel, the control pre-multiplies the alpha? (presuming that's the "munging" you refer to)?

    What if you had jumped to the logical conclusion that the bitmap you pass in should already be pre-multiplied? All of the Win32 Alpha functions expect pre-multiplied bitmaps, why is the static control the only thing that doesn't?

  12. Brian says:

    I agree with SimonRev — The handle to the secret image should never be returned from the control.  Internally it should hold 2 handles, the one the application specified, and the "secret copy" that it's actually using to render with.  The application is responsible for freeing it's image, and the control is responsible for freeing the secret copy.

  13. wqw says:

    Is anything like this happening with BM_SETIMAGE for buttons?

  14. foo says:

    As expected the MFC afxwin2.inl CStatic::SetBitmap method is just a simple wrapper around SendMessage, at least for VS2010. But I was kinda hoping there might be a SetBitmapS3krit that did the secret work inside. Oh well. stackoverflow.com/…/possible-memory-leak-using-gethbitmap-and-mfc-cstaticsetbitmap

  15. Harry Johnston says:

    @Joshua: you're saying that if app B was written after app A, then app A can't mess with app B's windows?  I don't see what would stop it.  You don't have to know any of the class names or whatnot ahead of time, you can just recusively enumerate the windows looking for interesting stuff.

    (For example, I have a simple-minded automation app that searches for a window containing a given piece of text, assumes it's a pushbutton, and sends BM_CLICK.  OK, that particular case is Mostly Harmless, but in principle I could have been trying to do something more dangerous.)

  16. Gabe says:

    There's no mention of the history of this, so I'm going to assume that the developer of the alpha handling code didn't realize that the original bitmap handle had to be returned. So he just went and did the easy thing of discarding the original bitmap and using the copy, with nobody noticing the leak until it was too late (i.e. the code had already reached ISVs).

  17. Leo Davidson says:

    [How do you fix this without introducing a double-free bug in code written to the old model? -Raymond]

    If we can assume code which is even aware of the secret copy would always compare it to the passed-in bitmap to see if it was different and needed freeing as well, then it's trivial to fix the control.

    If it needs to make a secret copy, it can store that internally (and knows it always has to free it; it never has to return it to anything else so nothing else could ever see or free it). If something asks for the image, the control can return the original handle, which it also keeps a copy of.

    To the outside world, the rules about when the secret copy made become "never", and the problem goes away. From what a previous commenter said about MSDN, it sounds like this is what already happened with version 6 of the common controls, unless MSDN or the comment are incorrect.

    Of course, there could be code out there which assumes the secret copy will *always* be made (maybe because they always pass an image meeting the magic criteria) so they always free the passed-in bitmap and the one the control returns, without checking they're the same. That code seems bad, since the criteria are arcane and could change (and from what a previous poster says, the criteria *have* changed according to MSDN) but maybe it is out there, I don't know.

    [You presume a level of forward-thinking that I think is not supported by evidence. (See: application compatibility.) I suspect a lot of applications, when they discover this problem, just blindly call DeleteObject to delete the bitmap that was previously leaking. -Raymond]
  18. immibis says:

    [We tried that before. It didn't work. -Raymond]

    That article only seems to apply to existing programs that need to remain compatible with everything. Is there a reason for *new* programs to not use STATIC2? Everything in Windows stays compatible. Maybe some accessibility software doesn't understand STATIC2, but that shouldn't be Microsoft's problem. (And if enough programs start using STATIC2, anyone who needs that accessibility software will have to migrate to a version that actually works)

    [Even new code needs to worry about this. Suppose you use a UI framework that uses global superclassing, or which uses hooks to trap all window creations so it can customize them. If you change the class name, the framework will not realize that STATIC2 should be treated the same as STATIC. -Raymond]
  19. Cesar says:

    So, there are two parts to this post.

    The first one can be simplified if you think of that control as two separate components: the control itself and a "initial image owner" which set the initial static image (and owns it). This simplifies the problem to "whoever set the image owns it" and "the initial image was set by magic Microsoft code, which owns it" (and will delete it when appropriate).

    The second one is much more confusing, but I think it can be simplified to "whenever you set the image, do a get to it; you own the returned handle too, just be careful because it might be the same you set" (inverting the thinking here: behave as if returning the same handle is the special case).

    Am I right with these simplifications?

  20. immibis says:

    [Even new code needs to worry about this. Suppose you use a UI framework that uses global superclassing, or which uses hooks to trap all window creations so it can customize them. If you change the class name, the framework will not realize that STATIC2 should be treated the same as STATIC. -Raymond]

    What kind of problems could this cause? Has it been tried? It's clear that the new STATIC2 windows would not have some of the added functionality (that the framework adds to STATIC windows) but this will happen right from the start, and the programmer has a choice between STATIC and STATIC2, with clear tradeoffs. (STATIC2 = easier programming; STATIC = works better with the UI framework they are using)

    ["Stupid Microsoft. Why did they have to change the class name? Now I have to completely redesign my program because I can't use my framework any more." -Raymond]
  21. jonwil says:

    If the code had been written as suggested in the first place (treat the "secret copy" as an implementation detail, return the original handle from STM_GETIMAGE, delete the copy if there is a STM_SETIMAGE and also delete the copy when the control is destroyed, there would have been no issues with leaks and no need for this special behavior.

    Obviously the behavior cant change now because of backwards compatibility for that's how it should have been done.

    [Not sure what you're getting at. "If the code had been written without this problem, then it wouldn't have this problem." -Raymond]
  22. Medinoc says:

    Raymond, if a "secret copy" is made, does the control still depend on the source image, or can it be deleted right away?

    As in, "Set the image, get the image, if they're different (and you don't need the base one for something else) delete the base one and keep the copy."

  23. Joshua says:

    ["Stupid Microsoft. Why did they have to change the class name? Now I have to completely redesign my program because I can't use my framework any more." -Raymond]

    [No matter what you do, somebody will call you an idiot. – Raymond] (blog post title)

    Chunk merge fail.

  24. immibis says:

    > ["Stupid Microsoft. Why did they have to change the class name? Now I have to completely redesign my program because I can't use my framework any more." -Raymond]

    You can continue using your framework exactly as you did before, or you can switch. Nothing is worse than before. Some things are better.

    [Tell that to everybody complaining that they can't use WinRT from desktop applications. -Raymond]
  25. Marc K says:

    Raymond,

    It looks like your posting software is messed up.  This April 1st post got into the February queue.  Good one, though.  You almost got me…

Comments are closed.