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.