The system manages the system image lists; don't go changing the art on the walls


A customer reported that their program ran on Windows XP just fine, but on Windows 7, its icons are all black boxes. They included a code fragment to demonstrate:

CBitmap *pBmp = new CBitmap;
pBmp->LoadBitmap(IDB_BITMAP);

CImageList m_ImageList;

SHFILEINFO sfi;
HIMAGELIST hHandle = (HIMAGELIST)SHGetFileInfo(_T(".txt"),
    FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi),
    SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
m_ImageList.Attach(hHandle);
int n = m_ImageList.Add(pBmp, RGB(0,255,0));

m_listctrl.SetImageList(&m_ImageList, LVSIL_NORMAL);

m_listctrl.InsertItem(0, _T("Sample"), n);

Okay, there are multiple things wrong here, but they all boil down to the same root cause: Modifying the system image list.

The system image lists are managed by the system. It adds images to the system image list when you do things like call SH­Get­File­Info with the SHGFI_SYS­ICON­INDEX flag, or if you host an Explorer Browser control or open a common file dialog. The system needs to keep all of the system image lists in sync, so that an index refers to the same image across all of the system image lists. It also needs to know how to regenerate each of those images, in case it needs to resize the images.

If you go in and make changes to the system image list (say, by adding new images), this bypasses all of the internal shell bookkeeping and gets everything out of sync. Once that happens, the results are unpredictable.

It so happens that Windows XP was far more forgiving of misuse of the system image lists, but optimizations introduced in Windows Vista made it less tolerant of abuse.

(The other bug here is that when the m_ImageList destructs, it will destroy the attached image list, which means destroying the system image list. This also tends not to end well, but my guess is that the customer never noticed because the m_ImageList was not destructed until the program was exiting anyway.)

The correct thing to do in the customer's scenario above is to create your own image list and put your custom images there.

Of course, if the image list had consisted entirely of app-provided bitmaps, then the customer probably would have done this in the first place. I suspect they are adding custom images to the system image list because they want their list view to be able to display both custom images and system images. In that case, it should create a custom image list, and put its custom images there. If it also wants to show a system image in the list view control, it should copy that image into their custom image list.

Comments (11)
  1. Medinoc says:

    Which reminds me, what was the initial purpose of the Shell Icons key? I remember using it back on Windows XP to give some custom 48*48-capable icons to some standard file types like .reg files, because I didn't like the upscaled look...

    1. Yuri Khan says:

      The Shell Icons registry key was available as far back as Windows 95.

      The original Windows 95 release only had 16- and 256-color icons. A post-release add-on, Windows 95 Plus!, contained (among other things) multiple hi-color icon themes which its configuration utility activated by rewriting that key.

      Perhaps supporting Plus! was the key’s sole intended purpose.

  2. and here my assumption was that the app was intentionally trying to manipulate the system image lists in a bastardized attempt to do some sort of theming

  3. jon says:

    What about Shell_GetCachedImageIndex ?

    1. skSdnW says:

      All *shell* functions that interact with the system imagelist are safe to use but that does not mean it is a good idea. On Windows 95 the system imagelist is shared by all processes and therefore it is pretty rude to add your own images just because you don't want to create a private imagelist. The system imagelist is supposed to make the system faster by caching icons that are used often.

      1. jon says:

        I think we've long passed the point where we have to worry about Windows 95 :)

  4. David Haim says:

    People should know by now that naming a class with a "C" prefix is not cool anymore. The nineties are long gone

    1. Ivan K says:

      I just assumed it was an mfc snippet, as opposed to qt or whatever. The _T() macro is a bit old school for Windows code but. But I bet Modern IDE's could make it look flash again.

  5. deskrule says:

    to be fair to people that "misuse image lists", once upon a time the shell assigned an empty "shell" imagelist to each process that requested one, so one got into the habit of using it more liberally. E.g. what is the "recommended" way to add a custom overlay image nowadays?

    1. skSdnW says:

      It was not completely empty, it contains the first 5 or so images from the "global" imagelist (off the top of my head; unassociated file, closed folder, shortcut arrow overlay). Every process gets a new copy because some apps are buggy and NT was all about security and stability. It was shared on 9x because a lot of stuff was shared by all processes there. There is a function in shell32 you can call to get the fully populated list.

      If you are not displaying the items exactly like the shell does then the shell imagelist is the wrong choice and you should use your own list (IMHO) but you might be able to pull it off with custom draw in listviews and treeviews.

Comments are closed.

Skip to main content