System Image List

How to access the System Image List (AKA the Shell Icon Cache)

Chinese translation at www.titilima.cn

Introduction

The System Image List (or Shell Icon Cache as it is sometimes called) is an icon resource maintained by the Windows Shell. This list is used by Explorer, among other applications, to display the icons for system objects, programs and document types.

{short description of image}

The list is no more than a simple HIMAGELIST (a standard Imagelist, which can be accessed using the standard image list API). Some applications may find it useful to display system supplied icons, rather than storing their own duplicate icons internally. So, the aim of this tutorial is to demonstrate how to access the System Image List and use it within your own applications.

Simple method first

If you've ever spent any time browsing around the MSDN help system, you may have stumbled over the SHGetFileInfo API call (exported from shell32.dll, available in Win9x and WinNT4/2000/XP). The function looks like this:

DWORD_PTR SHGetFileInfo(
    LPCTSTR     pszPath,              
    DWORD       dwFileAttributes,
    SHFILEINFO *psfi,
    UINT        cbFileInfo,
    UINT        uFlags
);

This function call can be used to get information for a specified file. The uFlags parameter specified what information is desired. Two particularly interesting values for this parameter are SHGFI_ICON and SHGFI_SYSICONINDEX. When these flags are specified, SHGetFileInfo returns a handle to the system imagelist, and stores the index of the requested icon type in the SHFILEINFO structure.

The following call demonstrates how this is possible:

SHFILEINFO shfi;
HIMAGELIST hSysImgList;
...
hSysImgList = (HIMAGELIST)SHGetFileInfo(
       "C:\", 
       0, 
       &shfi, 
       sizeof(SHFILEINFO), 
       SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_ICON);

Platform differences

There is a subtle, but important difference between Windows 9x and Windows NT family of operating systems. Under Windows 9x, SHGetFileInfo returns the handle to the system imagelist, which is shared by all processes. This imagelist is the same one used by Explorer and the Shell to display the system icons. That means that if one process corrupts the imagelist in any way, all processes (this includes Explorer) will be affected.

Under Windows NT (and 2000/XP) things are slightly different. Due to the system architecture, and also to maintain a robust operating environment, SHGetFileInfo will return a separate copy of the system imagelist to each process that requests it (one for each process). What's more, SHGetFileInfo does not return the whole system imagelist like it does with Windows 9x - only those icons requested in the call to SHGetFileInfo will be present in the imagelist - all the other entries are left blank. For 99% of situations this does not matter, but because we want to make a viewer application to view the whole imagelist, this presents us with a problem.

Locating the System Imagelist on all versions of Windows

I'm not going to beat around the bush. There is an undocumented API call inside Shell32.DLL called Shell_GetImageLists, which - you guessed it - returns a handle to the system imagelists (note the plural here - there are large and small icon versions). This undocumented API call is available on all versions of Windows that ship with shell32.dll.

BOOL WINAPI Shell_GetImageLists(HIMAGELIST *lphimlLarge, HIMAGELIST *lphimlSmall);

The one other API we need is FileIconInit, which is only available under Window NT/2000/XP. This is not a problem, because we only need to call is under Windows NT anyway.

BOOL WINAPI FileIconInit(BOOL bFullInit);

These APIs are not exported by name, only by ordinal:

; Shell32 undocumented functions
Shell_GetImageLists  @71
FileIconInit         @660

Armed with this information, we can now write a function to return the handles to the system imagelist. The first step is to make a couple of typedef's to make our lives easier.

typedef BOOL (WINAPI * SH_GIL_PROC)(HIMAGELIST *phLarge, HIMAGELIST *phSmall);
typedef BOOL (WINAPI * FII_PROC)   (BOOL fFullInit);

SH_GIL_PROC is short for Shell_GetIconLists_Procecedure, FII_PROC is short for FileIconInit_Procedure. Now we can locate the undocumented functions from inside Shell32.DLL so that we can call them:

HMODULE      hShell32;
SH_GIL_PROC  Shell_GetImageLists;
FII_PROC     FileIconInit;	

// Load SHELL32.DLL, if it isn't already
hShell32 = LoadLibrary("shell32.dll");
if(hShell32 == 0)
    return FALSE;

//
// Get Undocumented APIs from Shell32.dll:
//
Shell_GetImageLists  = (SH_GIL_PROC) GetProcAddress(hShell32, (LPCSTR)71);
FileIconInit         = (FII_PROC)    GetProcAddress(hShell32, (LPCSTR)660);

Note how the ordinals are simply passed as parameters to GetProcAddress. OK, now we have pointers to the functions we need we can proceed:

// Initialize imagelist for this process - function not present on win95/98
if(FileIconInit != 0)
    FileIconInit(TRUE);

// Get handles to the large+small system image lists!
Shell_GetImageLists(phLarge, phSmall);

// Do NOT free shell32.dll!

The last comment in the snippet above says "Do NOT free shell32.dll". This is important, because as soon as shell32.dll is unloaded, the system-image lists are destroyed, undoing all our hard work.

So, that's it! We now have handles to the large and small system imagelists, which we can display using the sample viewer application provided (see the link at the top of the page).

Never modify or delete the System Image List!

There is one more important piece of information that you should know before we go any further. You should never try to add icons to, remove icons from, or delete the System Imagelist. This is explained clearly in the documentation for SHGetFileInfo, and very good advise this is, too. However, there is one very easy way to accidently delete the System Imagelist.

One very common practise is for an application to create a TreeView or ListView, to display the names and details of files in the system (like Explorer, or WinZip for example). In order to display the system icons next to each entry in a TreeView / ListView, an application typically uses, for example, the LVM_SETIMAGELIST message (or the ListView_SetImageList macro) to assign an imagelist to the control. So, it would be entirely feasible for a program to obtain a handle to the system imagelist, and assign it to a control to use as it's own icon list. In fact, this is very probably how Explorer works.

Now for the problem. By default, when a ListView control is destroyed, it will automatically delete it's associated image list using the ImageList_Destroy API call. That's right, by default your program will obliterate the system imagelist without you even asking it to! Fortunately, there is a ListView control style (LVS_SHAREIMAGELISTS) which prevents a ListView control from deleting it's imagelist. (Make sure you set this style if you are in this situation!).

Mysteriously, the TreeView control does not suffer this problem - MSDN states that a TreeView will not destroy it's associated imagelist, so only the ListView control needs special attention.

Obviously, if your program runs under Windows NT/2000/XP, accidently deleting the system imagelist is not very serious, because you are only deleting your own process's copy of the imagelist. However, under Windows 95/98/ME, deleting the imagelist will have disasterous consequences. Well, just try it and see!

Conclusion

Obtaining the complete system imagelist could have been tricky, especially if we had had to resort to hacking the icons directly from the cached disk file they are stored in. Fortunately, there are a couple of undocumented API calls which came to the rescue. Of course, I can't claim credit for discovering these - I found this information on James Holderness's excellent website, which has more information on the sytem imagelist than I presented here, as well as loads more undocumented goodies! Check it out now!

Have fun!