Load HICON from the buffer (*.ico file)

微笑、不失礼 提交于 2020-08-19 10:17:05

问题


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;
}

回答1:


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




回答2:


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.



来源:https://stackoverflow.com/questions/41379285/load-hicon-from-the-buffer-ico-file

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!