首页  编辑  

The Shell Icon Cache

Tags: /超级猛料/API.Windows应用程序接口/未公开的API/   Date Created:

O   n Windows 95, just about everything that you see has an icon associated with it. Every time the shell displays a folder full of files it needs to obtain icons for each of those items from somewhere. Considering the expense of such an operation, it's obviously not something that it would want to repeat unnecessarily.

     By saving icons that it has already retrieved in a cache in memory, the shell is relieved of the need to constantly retrieve icons from disk. This can make a vast difference to the system performance, especially when accessing network drives and other slow media. The place where the shell stores its cached icons is called the System Imagelist,or Shell Icon Cache.

The Cache Structure

The cache is made up of a number of separate parts: adynamic structure array (called the icon table), a hash table,and two imagelists. The icons' location and other properties are stored in the icon table (the structure

can be see in Figure 1). The hash table isused to store the icon filenames so that they can be searched efficiently. The two imagelists contain the actual icon images -one list for the small icons, one for the

large.

struct {

 LPCSTR lpszFileName;

 UINT   nFileIndex;

 UINT   uFlags;

 UINT   nImageListIndex;

 UINT   uAccessTime;

};

Figure 1  Icon Table Item Structure

The lpszFileName used in the icon

table isn't just a regular string pointer - it's

actually a pointer into the hash table. Since hash table pointers can be compared directly, without ha-

ving to resort to a string comparison, the process of sear-

ching the icon table is made considerably faster. Also note

that the filename stored in the hash table is just a filename,

not a full path. If you try to use icons from two different

DLLs with the same name, you are not going to get the

results you were expecting.

     nFileIndex typically specifies the index of the icon (0

being the first icon in the file). It can also refer to a re-

source id though, in which case the value will be negated.

uFlags stores any GIL_ flags (see IExtractIcon::GetIcon-

Location) that were associated with the icon when it was

created. nImageListIndex is the index of the icon image in

the two image lists. uAccessTime specifies the last time

that the icon was accessed (the time is a relative mea-

surement in seconds, more or less).

     The setup of the cache on NT is basically the same as

Windows 95, except for one fundamental difference. On

Windows 95, there is only one cache that is shared by the

entire system - on NT there is a separate icon cache for

each process. This means that an imagelist index from a

cache in one process will not necessarily refer to the same

icon in a different process. This can be a major problem for

shell functions that require an imagelist index (e.g. the

SHCNE_UPDATEIMAGE event).    

Cache Lookups

You can add an icon to the cache with the undocumented

Shell_GetCachedImageIndex function (ordinal 72). As you

might has guessed it's ex-

ported from SHELL32.DLL

(see Figure 2 for the

function declaration).

lpszFileName points to a

null-terminated string spe-

cifying the name of an executable file, DLL, or icon file. nIconIndex specifies the

index of the icon to retrieve from the file (or resource id as

explained above). bSimulateDoc specifies whether to simu-

late a document icon or not.

int WINAPI Shell_GetCachedImageIndex(

 LPCSTR  lpszFileName,

 UINT    nIconIndex,

 BOOL    bSimulateDoc); shell32.dll index 72;

Figure 2  Shell_GetCachedImageIndex

The cache's icon table is searched to see if there is an

existing icon with the same filename and icon index. The

uFlags parameter must have the GIL_SIMULATEDOC flag

set if bSimulateDoc is TRUE. It must not have the GIL_

NOTFILENAME flag set though. All other flags are ignored.

If there isn't a matching icon in the icon table, a new entry

will be added. The function returns the icon's imagelist

index, or -1 if an error occured.

     Of course the imagelist index isn't much use by itself -

usually you would need the handles of the two imagelists as

well. That's where the Shell_GetImageLists function comes    

in (see Figure 3). It's also expor-

ted from SHELL32.DLL and the

ordinal is 71. lphimlLarge and

lphimlSmall point to variables

that will be filled in with the sys-

tem imagelist handles (do not attempt to destroy these handles). At the moment the

function will always returns TRUE, although future versions

may return FALSE in the event of an error.

BOOL WINAPI Shell_GetImageLists(

 HIMAGELIST *lphimlLarge,

 HIMAGELIST *lphimlSmall);

Figure 3  Shell_GetImageLists

Flushing Cache Entries

Obviously there's got to be a limit to the size of the cache,

but what happens when that limit is reached? Suprisingly,

nothing actually happens right away. However, every time

a shell window is closed, the shell will check the number of

icons in the cache. If it's over the limit and it has been more

than 15 minutes since the last time the cache was flushed,

the shell will attempt to free up some space.

     The decision as to whether an item should be removed

from the cache is dependent on the last time the icon was

accessed and the amount of time since the last cache flush.

For example, if the cache was last flushed 20 minutes ago,

any item that has an access time older than 10 minutes will

be freed (10 minutes being half of 20). The entry isn't ac-

tually removed from the icon table though - all that happens

is that the uAccessTime member is set to zero.

     Strictly speaking the entry isn't free to be reused yet. It

is still needed as a marker to show that the imagelist entry it

was using is now free. When the shell is looking for a free

entry in the imagelists it will search the icon table for any

item with an access time of zero, but with a non-zero

imagelist index. Once the imagelist entry has been reused,

both the uAccessTime member and the nImageListIndex

member can be set to zero. This marks the icon table entry

as free for reuse.

Flushing the Entire Cache

Other than freeing individual cache items, there are also

times when the entire cache needs to be cleared out. This

typically happens when you change the system icon size in

the Display Properties. Obviously the images in the image-

lists are now incorrectly sized, so the imagelists need to be

deleted and the whole cache rebuilt from scratch.

     You can force a full cache flush by manually changing

the the icon size yourself to something one pixel smaller,

broadcasting WM_SETTINGCHANGE and then setting the

icon size back to normal again (obviously followed by

another WM_SETTINGCHANGE). The message broadcast is

typically done something like this:

  SendMessageTimeout(

    HWND_BROADCAST,

    WM_SETTINGCHANGE,

    SPI_SETNONCLIENTMETRICS,

    (LPARAM)"WindowMetrics",

    SMTO_NORMAL|SMTO_ABORTIFHUNG,

    10000, NULL);

     You alter the icon size by manually changing its value

in the registry. The key to look at is:

  HKEY_CURRENT_USER\Control Panel\

    Desktop\WindowMetrics  

     The values for the icon size are Shell Icon Size and

Shell Small Icon Size (both are stored as strings - not

DWORDs). You only need to change one of them to cause

the refresh to happen (typically the large icon size). If those

values don't exist, the shell uses the SM_CXICON metric

(GetSystemMetrics) as the default size for large icons, and

half of that for the small icon size. If you're trying to cause

a refresh and the registry entry doesn't exist, you can just

assume that the size is set to SM_CXICON.

Persistence

Up to now, we have only discussed the cache as it appears

in memory. There is also a disk-based storage area that

enables the cache to persist from one session to the next.

When the system is shutdown via the shell (not something

like ExitWindows), it will will attempt to save the cache to

disk before proceeding the actual shutdown. You can also

force the save operation by calling SHGetFileInfo with

pszPath set to NULL (on NT, though, the save operation will

only work from within the main shell process).

     The disk cache is a hidden file in the windows directory

named ShellIconCache. It starts with a 60 byte header as

shown in Figure 4. Following that is a straight dump of the

icon table, including the 'free' entries.

Next come the actual strings for the

filenames (obviously the pointers in

the icon table are meaningless on

disk). NULL pointers are stored as

blank strings. Last of all come the

large and small imagelists, as stored

by the ImageList_Write function.

     Most of the items in the header

are used to determine the validity of

the cache when reloading it. The

structure size is set to zero when the

operation is first started and only set

correctly once everything else has

been commited to disk. That way you

should never have to worry about a

save operation that failed halfway

through. Things like the color depth

and the icon size make sure that the

cache matches the current display set-

tings.

0000 Structure size.

0004 Signature. 0x346E6957 or 'Win4'.

0008 Windows version number.

000C Windows build number.

0010 Number of items in the icon table.

0014 System color resolution (bpp).

0018 Icon color resolution.

001C Large icon width.

0020 Large icon height.

0024 Small icon width.

0028 Small icon height.

002C Time file was saved (time units are

relative).

0030 Time of last flush.

0034 Number of free images in icon table.

0038 Number of free entries in icon table.

Figure 4  Disk Cache Header

There is one small catch to this

operation though. The shell will not

proceed with the save if the icon table  

0000 Structure size.

0004 Signature. 0x346E6957 or 'Win4'.

0008 Windows version number.

000C Windows build number.

0010 Number of items in the icon table.

0014 System color resolution (bpp).

0018 Icon color resolution.

001C Large icon width.

0020 Large icon height.

0024 Small icon width.

0028 Small icon height.

002C Time file was saved (time units are

relative).

0030 Time of last flush.

0034 Number of free images in icon table.

0038 Number of free entries in icon table.

Figure 4  Disk Cache Header

is greater that the maximum size. However, as we've dis-

cussed earlier, the icon table never actually removes any

entries - it only marks them as being 'free' when they're

flushed. The result is that once the cache exceeds its maxi-

mum size, it will never be possible to save it (you could get

around this by doing a complete cache flush though).      

Default Initialization

Typically when you start up your system, the shell will

initialize the cache from the disk storage. However, if the

disk-based cache doesn't exist, or the color resolution and

icon size settings are incorrect, the cache will have to be

initialized in some other way. Rather than just start off with

an empty cache, the shell initializes it with 42 system icons

(43 on NT).

     For each of the icons, it checks for a value under the

following key:  

  HKEY_LOCAL_MACHINE\SOFTWARE\

    Microsoft\Windows\CurrentVersion\

    Explorer\Shell Icons

     The value named '0' corresponds to the first icon, the

value '1' the second icon, etc. Those values are strings of

the form 'filename,iconindex' specifying the location of the

icon. If the value doesn't exist, or the

specified icon can not be loaded, the

system will use icon ids 1 to 42 from

SHELL32.DLL (on NT the 43rd icon has

the id 172). Regardless of where they

are located, though, the icon table will

always have SHELL32.DLL as the file-

name and 0 to 42 for the index values.

     These system icons are of vital im-

portance to the shell. The folder icons,

the default file icons and the icons

used on the start menu are all obtained

from this set of icons (see Figure 5 for

some examples). These system icons

are also never flushed from the cache

so they are always guaranteed to be  there. Indices 28, 29 and 30 are set as the first three over-

lay images for the imagelist.

0, 1 Document (blank/associated)

2 Application

3, 4 Folder (plain/open)

5-11 Various drive types

19-24 Start menu items

28-30 Overlay images

31, 32 Default recycle bin (empty/full)

36 Program group

42 Common program group

35, 37, 39 Settings menu items

Figure 5  Some of the System Icons

On NT, though, there are a couple of differences to this

initialization process. The undocumented function, FileIcon-

Init (see Figure 6), handles the cache initialization explicitly.

If you don't call FileIconInit, the first call to an icon-related

function (such as Shell_GetImageLists), will result in File-    

IconInit being called with a parameter of

FALSE. This initializes the icon cache with

only the 3 overlay icons.

BOOL WINAPI FileIconInit(

 BOOL bFullInit);

Figure 6  FileIconInit

     If your application needs the cache

initialized with all the system icons it will have to call FileIconInit itself, with the bFullInit parameter

set to TRUE. This will initialize the cache with all 43 system

icons, or load the cache from disk if the disk-based cache is

available (the disk-based cache should always include the

system icons so it amounts to the same thing). You will also

need to call FileIconInit in response to any WM_SETTING-

CHANGE in case the icon size settings in the registry have

changed (as explained earlier, a size change would result in

a full cache flush).

     FileIconInit is exported from SHELL32.DLL with an

ordinal value of 660, but it only exists on Windows NT. If

your application needs to run on Windows 95 as well as

Windows NT, you will have to link to it at run-time with

GetProcAddress. The function returns TRUE if the cache

was already initialized or the initialization was successful. If

the initialization fails, it will return FALSE.    

Windows NT and Unicode Strings

As mentioned in previous articles, undocumented functions

on Windows NT typically require unicode strings where the

Windows 95 equivalent would use ansi. The only function

affected in this section is Shell_GetCachedImageIndex

which takes an LPCWSTR filename on NT.

     This unicode difference also applies to certain internal

structures on NT. The filenames in the hash table are ob-

viously stored as unicode strings, and the same thing applies

to the filenames when saved to the disk-based cache.