1

I'm just wondering, if there an API in Windows for loading the HICON from the byte array (buffer)? Let's say that I downloaded an *.ico file and I have the content of this file in some buffer. I want to be able to create the HICON from that buffer.

It is possible to load HICON from the *.ico which is placed on the hard drive, so I guess that there should be an equally simple way to do it from the memory buffer?

So far I found only 2 solutions but none of them is suitable for me.

The first one involved ATL usage and GDI+ (I'm using Rust and I don't have any bindings to GDI+).

The second one was based on usage of LookupIconIdFromDirectoryEx() and CreateIconFromResourceEx(). First I called LookupIconIdFromDirectoryEx() to get the offset for the correct icon and then I tried to call CreateIconFromResourceEx() (and CreateIconFromResource()) to get the HICON, but in all cases I receive a NULL value as a result, GetLastError() returns 0 though. My usage of those functions was based on this article (I tried to pass not only 0 as a second parameter, but also the size of the array buffer, excluding the offset, but it still fails).

The only remaining solution which I have in mind is to parse the *.ico file manually and then extract PNG images from it, then use the approach described here to create an icon from the PNG image. But it seems to be more like a workaround (Qt uses the similar approach though, maybe they were not able to find a different solution). Are there any simplier methods (maybe some WinAPI call) to get the things done?

UPD. Here is some test code which I tried (you should have an icon in order to run the example without crashes).

#include <cstdio>
#include <cstdlib>
#include <Windows.h>

#pragma comment(lib, "User32.lib")

int main()
{
    // Read the icon into the memory
    FILE* f = fopen("icon.ico", "rb");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    fseek(f, 0, SEEK_SET);
    char* data = (char*)malloc(fsize + 1);
    fread(data, fsize, 1, f);
    fclose(f);

    static const int icon_size = 32;
    int offset = LookupIconIdFromDirectoryEx((PBYTE)data, TRUE, icon_size, icon_size, LR_DEFAULTCOLOR);
    if (offset != 0) {
        HICON hicon = CreateIconFromResourceEx((PBYTE)data + offset, 0, TRUE, 0x30000, icon_size, icon_size, LR_DEFAULTCOLOR);
        if (hicon != NULL) {
            printf("SUCCESS");
            return 0;
        }
    }

    printf("FAIL %d", GetLastError());
    return 1;
}
Community
  • 1
  • 1
Daniel
  • 635
  • 1
  • 5
  • 22
  • Looks like there is a bug in your code then. We cannot help you with that, unless you provide a [mcve]. – IInspectable Dec 29 '16 at 12:08
  • Fix your code rather than give up – David Heffernan Dec 29 '16 at 12:36
  • @IInspectable, I did not add it initially, because it was basically the same as in the article I linked. But now I updated the description and attached the source code there. In my case it always prints "FAIL 0". – Daniel Dec 29 '16 at 13:47
  • @DavidHeffernan I would be glad to fix it and I tried several different scenarios, however it still does not work, that's why I decided to ask. I've just attached the code which I tried to use, unfortunately it always fails. – Daniel Dec 29 '16 at 13:48
  • This is not a [mcve]. It's missing the input. If you cannot upload the icon, add a static array to the code, initialized to the smallest icon that exhibits the error. In the meantime you could double-check, that your icon indeed contains a 32x32 image. – IInspectable Dec 29 '16 at 14:00
  • @IInspectable, thanks for your suggestion, I tested the code on several other icons and I found an answer on my question. It seems that there is a bug in `LookupIconIdFromDirectoryEx()`, that's why it failed sometimes. Now the solution is clear for me. I've added an answer below (I had to answer my own question), check it out if you're interested in a result. – Daniel Dec 30 '16 at 18:21
  • @DavidHeffernan, finally I figured out that my code was correct, so there is nothing to fix there :) The problem was in API function, it has a bug inside (you can find details inside my answer below the question). – Daniel Dec 30 '16 at 18:23

3 Answers3

2
CreateIconFromResourceEx((PBYTE)data + offset, 0, ...)

The second parameter should not be zero. Windows doesn't know how far it can read the buffer without causing buffer overflow. Apparently Windows does allow for this error in some cases, but maybe it's not prepared in the case of PNG files, it stops when it doesn't see a BITMAPINFOHEADER

Just provide the maximum available buffer size, that should solve the problem with PNG files:

CreateIconFromResourceEx((PBYTE)data + offset, fsize - offset, ...)

Documentation says that LookupIconIdFromDirectoryEx expects resource data. This API does appear to work with icon files, it returns the offset for the first icon. Either way it doesn't appear to have a bug based on what the documentation says.

It's better to calculate the offset manually. It appears you already know how to calculate the offset values. You can simply calculate the offset as follows, based on ICONDIRENTRY

WORD icon_count = 0;
fseek(f, 2 * sizeof(WORD), SEEK_SET);
fread(&icon_count, sizeof(WORD), 1, f);
int offset = 3 * sizeof(WORD) + sizeof(ICONDIRENTRY) * icon_count;

sizeof(ICONDIRENTRY) is 16. The icon file starts with 3 WORD values, the third value is icon_count, followed sizeof(ICONDIRENTRY) * icon_count bytes, followed by the bytes for the first HICON

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • thanks for your comment, did not expect to see something new after almost a year ;) Well, actually as I mentioned in my original post, the function failed regardless of that `0` value. As for the bug, I still think that `LookupIconIdFromDirectoryEx` contains a bug, because it returned a wrong value on a valid resource data. The output of the function depends on the size of the icon which is being requested (when the size of the icon exceeds `u16::max()`, the function starts to return wrong values / fails). Such behavior is not mentioned in documentation. – Daniel Aug 13 '18 at 13:52
0

I found a solution. Actually after a bit of research it turned out that the code, which I placed inside the sample, is indeed correct.

There is a bug in WinAPI function LookupIconIdFromDirectoryEx(). I've noticed that for some icons I can get the correct icon and set it up, but for others it fails either on the later stage CreateIconFromResourceEx(), or earlier on LookupIconIdFromDirectoryEx(). I've noticed that sometimes the function fails to find the icon even though the icon is inside a file. Sometimes the function returned the same value for the different icons inside an icon file.

I made several rounds of tests and parsed the format of each icon file myself based on the format definition. Then I compared the actual offsets to the values returned by LookupIconIdFromDirectoryEx().

Let's say we have 2 icons: A and B.

The A icon in my case contained 5 images, the entries inside the ICO file were placed in the following order:

  1. 256x256 PNG
  2. 128x128 BMP
  3. 64x64 BMP
  4. 32x32 BMP
  5. 16x16 BMP

The B icon contained 7 images, they were placed in the following order:

  1. 16x16 BMP
  2. 32x32 BMP
  3. 48x48 BMP
  4. 64x64 BMP
  5. 128x128 BMP
  6. 256x256 BMP

The results of the LookupIconIdFromDirectoryEx() for each of the icons can be found below.

Icon A:

  1. 86
  2. 9640
  3. 9640
  4. 9640
  5. 9640

Icon B:

  1. 102
  2. 1230
  3. 5494
  4. 15134
  5. NOT FOUND (function failed and returned 0)
  6. NOT FOUND (function failed and returned 0)
  7. NOT FOUND (function failed and returned 0)

I parsed the actual format, according to the definition in wikipedia (the tables below are contain the icon entries, each row is a separate entry, each column is a field for this entry) for both icon files.

The actual layout of A is:

W     H     *    *    *   **     SIZE     OFFSET
------------------------------------------------
0     0     0    0    1   32     43253    86 
128   128   0    0    1   32     67624    43339 
48    48    0    0    1   32     9640     110963 
32    32    0    0    1   32     4264     120603 
16    16    0    0    1   32     1128     124867 

The actual layout of B is:

W     H     *    *    *   **     SIZE     OFFSET
------------------------------------------------
16    16    0    0    0   32     1128     102 
32    32    0    0    0   32     4264     1230 
48    48    0    0    0   32     9640     5494 
64    64    0    0    0   32     16936    15134 
128   128   0    0    0   32     67624    32070 
0     0     0    0    0   32     270376   99694 

So you can clearly see, that in case of A only the offset for the first image was idenfied correct, offsets for other images were incorrect and equal to the ... size of the 3rd image (??), maybe just a coincidence.

In case of the second image all offsets were correct until we reached 128x128 image, which was not even found.

The common thing between those 2 cases is that the function started to behave strange after parsing 128x128 icon, and here is an interesting thing - look on the size of 128x128 icon and compare it to the size of the other images. In both cases the size of the 128x128 image does not fit in 2 bytes. Right after parsing the icon entry in which the size was bigger than 2 bytes, the function behavior is undefined. Judging from this data I can assume that somewhere in the code they expect that the size of the icon cannot be bigger than 2 bytes. In case if the size is bigger, the behavior is undefined.

I used ImageMagick to reassemble a new icon which does not have such image inside and now the function works correct in all cases.

So I can definitely say that there is a bug inside LookupIconIdFromDirectoryEx() implementation.

UPD. Icons can be found here: A icon, B icon.

Daniel
  • 635
  • 1
  • 5
  • 22
  • Highly improbable, that the Windows API has a bug here. More likely that you are misinterpreting the results. Icons with PNG images are very different from icons with BMP images. Raymond Chen has a multi-part series on the ICO file format. [The evolution of the ICO file format, part 4: PNG images](https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473) may be helpful in understanding the results you are observing. – IInspectable Dec 30 '16 at 19:30
  • @IInspectable, but the results show that it does not work properly even if I use the icon which contains only BMP images. Have a look on the icon file `B` in my answer, the function failed when I tried to search for an icon which is bigger than 128x128 pixels. – Daniel Dec 30 '16 at 20:36
  • I initially asked about the way of loading the `HICON` from the ICO file, but it seems that there are no other reliable solutions (at least I have not heard any answers from others, I also was not able to find a better solution myself). The only way which worked for me was the one I described. Then I made a small research to find a reason why it fails. Strange that my answer received so many down-votes without explaining the reason for them. – Daniel Dec 30 '16 at 20:40
  • It is so much more likely, that your input is wrong, rather than there being a bug in the Windows API. But we cannot help you here without seeing your input. I don't know, why this proposed answer received 2 down-votes either, and I'd like to see a comment as to why as well. – IInspectable Dec 31 '16 at 13:04
  • @IInspectable I would mark the topic as solved as the research I did has answered (more or less) my question and helped me to understand how to properly workaround it. However if there any other better solutions, I'm always interested to see them (for now I will stick to the results I've had so far). I also updated my answer and attached the link to the archive (contains 2 icons, with and without PNG inside), so if you are interested, you can have a look on them. But anyway, thanks for your suggestions and remarks. – Daniel Jan 03 '17 at 14:27
  • Just tried to download your icon files, and received the following message: *"This file is dangerous, so Chrome has blocked it."* This really looks like your icons are broken and/or tampered with to intrude systems. Try to move them out of a ZIP archive, and link them using Stack Overflow's built-in imgur.com interface (available through the online editor). – IInspectable Jan 03 '17 at 14:34
  • @IInspectable, strange, it's just an archive of 2 icons. I receive the same error from Chrome though. I've tried to use imgur.com interface, but it did not accept images, because icon files are not supported. I'll try to re-upload the icons (without archiving them) on a different service in a minute. – Daniel Jan 04 '17 at 19:32
  • *"In both cases the size of the 128x128 image does not fit in 2 bytes"* 128 does fit in two bytes. 256 doesn't, it get chopped down to zero. But the PNG data itself contains the correct dimension, so this doesn't cause too much problem. Probably Windows will attempt to treat this zero as 256. – Barmak Shemirani Aug 12 '18 at 06:32
  • @BarmakShemirani hm, I referred to the size of the 128x128 image actually, in both cases the size of that image was 67624 and that's when the things started to behave in a strange manner. – Daniel Aug 13 '18 at 13:46
  • I see what you mean. I couldn't make 128x128 that large, I did make 256x256 with 200Kb size and `LookupIconIdFromDirectoryEx` worked fine. My first comment was wrong anyway, ignore it. I meant width & height are stored as 1 byte, it runs in to problem for 256. The actual icon size is stored in 4 bytes. See this [link for `ICONDIRENTRY`](https://msdn.microsoft.com/en-us/library/ms997538.aspx). There is a related structure which replaces `dwImageOffset` with a 2-byte value (`GRPICONDIRENTRY`), maybe that's what you were thinking about. – Barmak Shemirani Aug 13 '18 at 15:47
0

LookupIconIdFromDirectoryEx - is used to parse PE file resource directories (EXE or DLL) of type RT_GROUP_ICON and select proper RT_ICON resource ID that corresponds provided size and current screen bits-per-pixel. RT_GROUP_ICON consists of GRPICONDIR/GRPICONDIRENTRY structures:

typedef struct 
{
   WORD            idReserved;   // Reserved (must be 0)
   WORD            idType;       // Resource type (1 for icons)
   WORD            idCount;      // How many images?
   GRPICONDIRENTRY idEntries[1]; // The entries for each image
} GRPICONDIR, *LPGRPICONDIR;

typedef struct
{
   BYTE   bWidth;               // Width, in pixels, of the image (0 if 256)
   BYTE   bHeight;              // Height, in pixels, of the image (0 if 256)
   BYTE   bColorCount;          // Number of colors in image (0 if >=8bpp)
   BYTE   bReserved;            // Reserved
   WORD   wPlanes;              // Color Planes
   WORD   wBitCount;            // Bits per pixel
   DWORD  dwBytesInRes;         // how many bytes in this resource?
   WORD   nID;                  // the ID
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY;

More info on the exact RT_GROUP_ICON binary format here and here. Also documented here as NEWHEADER/RESDIR.

LookupIconIdFromDirectoryEx is not intended to load ICO files because they have similar but slightly different binary representation:

typedef struct
{
    WORD           idReserved;   // Reserved (must be 0)
    WORD           idType;       // Resource Type (1 for icons)
    WORD           idCount;      // How many images?
    ICONDIRENTRY   idEntries[1]; // An entry for each image
} ICONDIR, *LPICONDIR;

typedef struct
{
    BYTE        bWidth;          // Width, in pixels, of the image (0 if 256)
    BYTE        bHeight;         // Height, in pixels, of the image (0 if 256)
    BYTE        bColorCount;     // Number of colors in image (0 if >=8bpp)
    BYTE        bReserved;       // Reserved (must be 0)
    WORD        wPlanes;         // Color Planes
    WORD        wBitCount;       // Bits per pixel
    DWORD       dwBytesInRes;    // How many bytes in this resource?
    DWORD       dwImageOffset;   // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;
  • GRPICONDIRENTRY has WORD nId - the RT_ICON identifier that can be passed to FindResource, LoadResource and LockResource to obtain a pointer to the icon bits (usually pointer to ICONIMAGE structure).
  • ICONDIRENTRY in ICO has DWORD dwImageOffset - where icon bits data can be found from the beginning of the ICO file.

It seems there is no API that can do what LoadIcon/LoadImage does for ICO files but works with a memory pointer. If you really want to load ICO from memory then you can parse ICONDIR/ICONDIRECTORY ICO structs yourself, select needed bitmap from it (there can be several resolutions and bpp in one ICO) and only then call a CreateIconFromResourceEx with a proper icon bits. Please also note that CreateIconFromResourceEx expects that presbits pointer is aligned to 8-byte boundary (which is not always true for dwImageOffset in ICO file - so you have to copy icon data to aligned buffer before call to this API).

BONUS: Looks like CreateIconFromResourceEx can convert to HICON not only ICONIMAGE structures but also ANI cursors and PNG-coded icon frames (used since Windows Vista).

PS: Here you can find code how to do manually load resources from PE file: https://stackoverflow.com/a/20731449/1795050 You can craft the same but for ICONDIR/ICONDIRECTORY structures.

Reference:

DJm00n
  • 1,083
  • 5
  • 18