The format of icon resources


It's been a long time since my last entry in the continuing sporadic series on resources formats. Today we'll look at icons.

Recall that an icon file consists of two parts, an icon directory (consisting of an icon directory header followed by a number of icon directory entries), and then the icon images themselves.

When an icon is stored in resources, each of those parts gets its own resource entry.

The icon directory (the header plus the directory entries) is stored as a resource of type RT_GROUP_ICON. The format of the icon directory in resources is slightly different from the format on disk:

typedef struct GRPICONDIR
{
    WORD idReserved;
    WORD idType;
    WORD idCount;
    GRPICONDIRENTRY idEntries[];
} GRPICONDIR;

typedef struct GRPICONDIRENTRY
{
    BYTE  bWidth;
    BYTE  bHeight;
    BYTE  bColorCount;
    BYTE  bReserved;
    WORD  wPlanes;
    WORD  wBitCount;
    DWORD dwBytesInRes;
    WORD  nId;
} GRPICONDIRENTRY;

All the members mean the same thing as in the corresponding ICONDIR and IconDirectoryEntry structures, except for that mysterious nId (which replaces the dwImageOffset from the IconDirectoryEntry). To unravel that mystery, we need to look at where the rest of the icon file went.

In the icon file format, the dwImageOffset represented the location of the icon bitmap within the file. When the icon file is converted to a resource, each icon bitmap is split off into its own resource of type RT_ICON. The resource compiler auto-assigns the resource IDs, and it is those resource IDs that are stored in the nId member.

For example, suppose you have an icon file with four images. In your resource file you say

42 ICON myicon.ico

The resource compiler breaks the file into five resources:

Resource type Resource Id Contents
RT_GROUP_ICON 42 GRPICONDIR.idCount = 4
GRPICONDIRENTRY[0].nId = 124
GRPICONDIRENTRY[1].nId = 125
GRPICONDIRENTRY[2].nId = 126
GRPICONDIRENTRY[3].nId = 127
RT_ICON 124 Pixels for image 0
RT_ICON 125 Pixels for image 1
RT_ICON 126 Pixels for image 2
RT_ICON 127 Pixels for image 3

Why does Windows break the resources into five pieces instead of just dumping them all inside one giant resource?

Recall how 16-bit Windows managed resources. Back in 16-bit Windows, a resource was a handle into a table, and obtaining the bits of the resource involved allocating memory and loading it from the disk. Recall also that 16-bit Windows operated under tight memory constraints, so you didn't want to load anything into memory unless you really needed it.

Therefore, looking up an icon in 16-bit Windows went like this:

  • Find the icon group resource, load it, and lock it.
  • Study it to decide which icon image is best.
  • Unlock and free the icon group resource since we don't need it any more.

  • Find and load the icon image resource for the one you chose.
  • Return that handle as the icon handle.

Observe that once we decide which icon image we want, the only memory consumed is the memory for that specific image. We never load the images we don't need.

Drawing an icon went like this:

  • Lock the icon handle to get access to the pixels.
  • Draw the icon.
  • Unlock the icon handle.

Since icons were usually marked discardable, they could get evicted from memory if necessary, and they would get reloaded the next time you tried to draw them.

Although Win32 does not follow the same memory management model for resources as 16-bit Windows, it preserved the programming model (find, load, lock) to make it easier to port programs from 16-bit Windows to 32-bit Windows. And in order not to break code which loaded icons from resources directly (say, because they wanted to replace the icon selection algorithm), the breakdown of an icon file into a directory + images was also preserved.

You now know enough to solve this customer's problem:

I have an icon in a resource DLL, and I need to pass its raw data to another component. However, the number of bytes reported by Size­Of­Resource is only 48 instead of 5KB which is the amount actually stored in the resource DLL. I triple-checked the resource DLL and I'm sure I'm looking at the right icon resource.

Here is my code:

HRSRC hrsrcIcon = FindResource(hResources,
                     MAKEINTRESOURCE(IDI_MY_ICON), RT_GROUP_ICON);
DWORD cbIcon = SizeofResource(hResources, hrsrcIcon);
HGLOBAL hIcon = LoadResource(hResources, hrsrcIcon);
void *lpIcon = LockResource(hIcon);
Comments (12)
  1. This person is getting the icon directory header and entries only. Which happens to just contain 3 enteries if I calculated that correctly.

  2. Err, forgot to mention, to get the icon itself, the customer would need to search for the RT_ICON resource type, not RT_GROUP_ICON.

  3. So I am going to vote for a future article on the history of "the icon selection algorithm". I know that there is some amount of unofficial stuff on-line that suggests that the order of the entries in the icon directory matters for certain version of Windows.

  4. Ken Hagan says:

    @Charles: I can interpret your suggestion in two ways.

    Either different versions of Windows make different-but-equally-legitimate choices depending on the order of entries, or some versions of Windows make a definitely-bad choice unless the order is such-and-such. Since I've never knowingly used a tool that let me control the order of entries, the latter possibility might be something worth knowing about.

  5. I guess he'd need to reconstruct the icon file format by hand by gathering all the different pieces together?

    Or better, don't store the resource as an ICON, so that the compiler doesn't split it apart?

  6. laonianren says:

    @Charles & Ken

    This article (msdn.microsoft.com/…/ms997538.aspx) says that Windows chooses the application icon thus:

    "Windows NT simply chooses the first resource listed in the application's RC script. On the other hand, Windows 95's algorithm is to choose the alphabetically first named group icon if one exists. If one such group resource does not exist, Windows chooses the icon with the numerically lowest identifier."

    Compiled resources are split into named and numbered resources (names first), and each category is sorted by the resource identifier.  So you can't normally control the order of resources, other than by changing the IDs.

    Which suggests that Windows 9x was looking at RT_GROUP_ICON resources, which are ordered by ID, and Windows NT was (is) looking at RT_ICON resources, whose IDs can match the order of icons in the RC file because they are auto-generated.

  7. Troy Martin says:

    Raymond, a comment on the post that is the second link you included in today's post links to a Microsoft Support document (support.microsoft.com/default.aspx) that says "No 16-bit code can run, except for recognized InstallShield and Acme installers (these are hard-coded in Wow64 to allow them to work)." How does WoW64 support these legacy fragments of code in a processor mode that does not support v86 mode?

    [Hm, I thought it was obvious. I guess not… (Hint: Why does it have to be "recognized"?) -Raymond]
  8. Troy Martin says:

    [Hm, I thought it was obvious. I guess not… (Hint: Why does it have to be "recognized"?) -Raymond]

    My train of thought leads me to the idea that WoW64 extracts the contents of a "recognized" installer and puts up its own install method or translates it to a newer InstallShield platform. I'm probably wrong, though.

    …ooor there's a software emulator or translator built into Windows for the specific purpose of executing enough BIOS crud to get through a tried-and-true stable installer. That would be nifty.

  9. Cherry says:

    <a href="my.safaribooksonline.com/…/id3362927 book</a> says:

    <blockquote>"Whenever a 16-bit process is about to be created using the CreateProcess() API, Ntvdm64.dll is loaded and control is transferred to it to inspect whether the 16-bit executable is one of the supported installers. If it is, another CreateProcess is issued to launch a 32-bit version of the installer with the same command-line arguments."</blockquote>

    I assume these installers were not packed at all, instead they had an executable file and some data files, so all Windows needs to do is call the 32-bit version of the installer (which is probably shipped with Windows) so that the 32-bit installer may install the data files originally belonging to the 16-bit installer.

    Thus, the wording "No 16-bit code can run, except for…" is technically incorrect, since no 16-bit code will ever be actually running.

  10. Cherry says:

    Sorry, I assumed a limited HTML set was allowed.

    my.safaribooksonline.com/…/id3362927

    This is the complete link.

  11. Troy Martin says:

    After a bit of poking around, I found that in driverland you can poke HAL.DLL to emulate a real mode BIOS environment (at least, on Windows XP x64): x86asm.net/…/calling-bios-from-driver-in-windows-xp-x64

  12. 640k says:

    I've never understood why (32/64-bit) windows is trying to hide the bitmap data. If I didn't know better, I would have guessed it was because it was only on disk and/or in graphics memory. But that's not true, it has never been true. It's allocated in the process, gimme my data!

Comments are closed.