Difference between ExtractIcon and ExtractAssociat

2020-03-08 05:23发布

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:

enter image description here

Then I'll be interested in a 48x48 size icon:

enter image description here

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:

enter image description here

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?

1条回答
叛逆
2楼-- · 2020-03-08 05:59

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:

  1. 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;
    }
    
  2. 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.

查看更多
登录 后发表回答