从缓冲区加载 HICON(*.ico 文件)

Load HICON from the buffer (*.ico file)

我只是想知道,Windows 中是否有一个 API 用于从字节数组(缓冲区)加载 HICON?假设我下载了一个 *.ico 文件并且我在某个缓冲区中有这个文件的内容。我希望能够从该缓冲区创建 HICON

可以从硬盘驱动器上的 *.ico 加载 HICON,所以我想应该有一种同样简单的方法从内存缓冲区加载?

到目前为止,我只找到了 2 个解决方案,但其中 none 个适合我。

第一个 involved ATL usage and GDI+(我使用的是 Rust,我没有绑定到 GDI+)。

第二个基于 LookupIconIdFromDirectoryEx()CreateIconFromResourceEx() 的用法。首先我调用 LookupIconIdFromDirectoryEx() 来获取正确图标的偏移量,然后我尝试调用 CreateIconFromResourceEx()(和 CreateIconFromResource())来获取 HICON,但在所有情况下我都会收到 NULL 值,但 GetLastError() returns 0。我对这些函数的使用是基于 this article (我试图不仅将 0 作为第二个参数传递,而且还传递数组缓冲区的大小,不包括偏移量,但它仍然失败)。

我想到的唯一剩余解决方案是手动解析 *.ico 文件,然后从中提取 PNG 图像,然后使用描述的方法 here 从 PNG 创建图标图片。但它似乎更像是一种解决方法(虽然 Qt 使用类似的方法,但也许他们无法找到不同的解决方案)。是否有任何更简单的方法(也许是一些 WinAPI 调用)来完成这些事情?

UPD. 这是我试过的一些测试代码(你应该有一个图标,以便 运行 这个例子不会崩溃)。

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

我找到了解决办法。实际上,经过一些研究后发现,我放置在示例中的代码确实是正确的。

WinAPI 函数 LookupIconIdFromDirectoryEx() 中存在错误。我注意到,对于某些图标,我可以获得正确的图标并进行设置,但对于其他图标,它要么在后期 CreateIconFromResourceEx() 时失败,要么在 LookupIconIdFromDirectoryEx() 之前失败。我注意到有时函数无法找到图标,即使图标在文件中也是如此。有时函数会为图标文件中的不同图标返回相同的值。

根据format definition测试了好几轮,自己解析了每个图标文件的格式。然后我将实际偏移量与 LookupIconIdFromDirectoryEx().

返回的值进行比较

假设我们有 2 个图标:AB

我的 A 图标包含 5 张图像,ICO 文件中的条目按以下顺序放置:

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

B 图标包含 7 张图片,它们按以下顺序放置:

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

每个图标的 LookupIconIdFromDirectoryEx() 结果如下。

图标A:

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

图标B:

  1. 102
  2. 1230
  3. 5494
  4. 15134
  5. 未找到(函数失败并返回 0
  6. 未找到(函数失败并返回 0
  7. 未找到(函数失败并返回 0

我根据definition in wikipedia(下表包含图标条目,每行是一个单独的条目,每列是该条目的一个字段)为两个图标文件解析了实际格式。

A的实际布局是:

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 

B的实际布局是:

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 

所以你可以清楚地看到,在 A 的情况下,只有第一张图片的偏移量被识别为正确的,其他图片的偏移量不正确并且等于...第三张图片的大小( ??), 也许只是巧合。

在第二张图片的情况下,所有偏移都是正确的,直到我们达到 128x128 的图片,甚至找不到。

这两种情况的共同点是函数在解析 128x128 图标后开始表现奇怪,这是一件有趣的事情 - 查看 128x128 图标的大小并将其与其他图像的大小进行比较。在这两种情况下,128x128 图像的大小都不适合 2 个字节。在解析大小大于 2 个字节的图标条目后,函数行为未定义。从这些数据来看,我可以假设在代码的某处他们期望图标的大小不能大于 2 个字节。如果尺寸更大,则行为未定义。

我使用 ImageMagick 重新组装了一个内部没有此类图像的新图标,现在该功能在所有情况下都可以正常工作。

所以我可以肯定地说 LookupIconIdFromDirectoryEx() 实现中存在错误。

更新。可以在此处找到图标:A icon, B icon.

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

第二个参数不能为零。 Windows不知道它能读多远的缓冲区而不会导致缓冲区溢出。显然 Windows 在某些情况下确实允许此错误,但对于 PNG 文件可能没有准备好,当它没有看到 BITMAPINFOHEADER

时它会停止

只需提供最大可用缓冲区大小,即可解决 PNG 文件的问题:

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

文档说 LookupIconIdFromDirectoryEx 需要资源数据。 API 似乎确实适用于图标文件,它 returns 第一个图标的偏移量。无论哪种方式,根据文档所述,它似乎都没有错误。

最好手动计算偏移量。看来您已经知道如何计算偏移值。您可以根据 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)是16。图标文件以3个WORD值开始,第三个值是icon_count,后面是sizeof(ICONDIRENTRY) * icon_count字节,接下来是字节首先 HICON