Say, if I want to extract an icon out of a stock Windows executable. I can get that icon ID by opening it in Visual Studio:
Then I'll be interested in a 48x48 size icon:
So my assumption was to do:
HICON hIcons[4];
::ExtractIconEx(L"mstsc.exe", -13011, hIcons, NULL, 4);
hIconLogo = hIcons[3];
but when I run it, the method returns only 3 icons:
and only one of them is a 32x32 version of what I need.
I then found ExtractAssociatedIconEx API that I called as such:
WORD wIcnId = -13011;
WORD wIcnInd = 3;
hIconLogo = ::ExtractAssociatedIconEx(hInst, L"mstsc.exe", &wIcnInd, &wIcnId);
but that too gives me some other icon that I did not expect.
So what is the difference between those two APIs? And what am I doing wrong?
The ExtractIconEx
function can return only two sizes of icons: large and small. Those are relative sizes, defined by the environment. A "large" icon is classically 32x32 pixels, but may be larger on certain system configurations. A "small" icon is classically 16x16 pixels, but the same caveat applies. The only guarantee is that a "small" icon is, well, smaller than a "large" icon. If you want to know the actual size on your system, you call the GetSystemMetrics
function with SM_CXICON
and SM_CYICON
for "large" icons, or SM_CXSMICON
and SM_CYSMICON
for "small" icons.
The operating system uses "small" and "large" icons everywhere internally; most of the APIs deal only with "small" and "large" (also occasionally known as "big" icons). When you set an icon for a window, for example, you set either a "small" icon or a "big" icon. Those are your only two choices.
The ExtractIconEx
function sets the phIconLarge
parameter to a pointer to an array of handles to large icons. The phIconSmall
parameter is set to a pointer to an array of handles to small icons. Since you passed NULL
for the phIconSmall
parameter, you didn't get any small icons. hIcons
filled with handles to the "large" icons in the file, which, on your system, are different bit-depths of 32x32 icons.
The ExtractAssociatedIcon
function (and its Ex brother) returns only "large" icons. So you should be getting the same results when you call it as you do for the way that you call ExtractIconEx
. I'm not really sure if you're saying that it is giving you different results or not. It might have something to do with the index. Negative indices mean something special to ExtractIconEx
, but I'm not sure if they are valid for ExtractAssociatedIcon
. The documentation doesn't give much of a hint.
The SHGetFileInfo
function, although more powerful in a number of senses, including the ability to extract icons from any file-system object, has the same fundamental limitation: it gives you choices of SHGFI_LARGEICON
and SHGFI_SMALLICON
.
If you need to extract icons of custom sizes (i.e., something other than the system's "small" and "large" sizes), then you'll need to do more work. There are essentially two options:
Call the SHGetImageList
function, which is another shell helper function, but one that retrieves a shell image list containing icons. It gives you far more options for icon sizes: SHIL_SMALL
(generally 16x16), SHIL_LARGE
(generally 32x32), SHIL_EXTRALARGE
(generally 48x48), and SHIL_JUMBO
(generally 256x256—only on Vista and later). So if you ask for SHIL_EXTRALARGE
, you'll get the 48x48 icons that you're looking for.
You'll still need the SHGetFileInfo
function here, but this time it will be to retrieve the index of the desired icon in the shell image list. Retrieve that with the SHGFI_SYSICONINDEX
option.
Completely untested sample code, never touched by a compiler:
HICON ExtractExtraLargeIcon(LPCTSTR pszPath)
{
// Determine the index of the desired icon
// in the system image list.
SHGETFILEINFO sfi;
SHGetFileInfo(pszPath, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX);
// Retrieve the system image list.
// (To get 48x48 icons, we use `SHIL_EXTRALARGE`.)
IImageList* piml;
if (SHGetImageList(SHIL_EXTRALARGE, IID_IImageList, (void**)&piml) == S_OK)
{
HICON hIcon;
if (piml->GetIcon(sfi.iIcon, ILD_TRANSPARENT, &hIcon) == S_OK)
{
return hIcon;
}
}
// Oops! We failed.
return NULL;
}
Your other option is to extract the desired size icon from the file yourself. Although the icon resource format is well-documented, and various people have used this knowledge to write a bunch of ugly extraction code and posted it on the Internet, there is an easier way: SHDefExtractIcon
.
As Raymond Chen blogged about some time ago, SHDefExtractIcon
is your more powerful fallback if IExtractIcon::Extract
(which is what the code sample above attempts to use) fails. The power of this function is its nIconSize
parameter, which specifies the actual size of the icon that you want to extract.
Adapting Raymond's example:
HICON ExtractArbitrarySizeIcon(LPCTSTR pszPath, int size)
{
HICON hIcon;
if (SHDefExtractIcon(pszPath, 1, 0, &hIcon, NULL, size) == S_OK)
{
return hIcon;
}
return NULL; // failure
}
Whatever you do, remember that whenever an API function returns an HICON, it is transferring ownership of that resource to you. This means that, when you are finished with the icon, you must destroy it by calling the DestroyIcon
function to avoid a leak.