【问题标题】:Load HICON from the buffer (*.ico file)从缓冲区加载 HICON(*.ico 文件)
【发布时间】:2017-05-13 18:06:01
【问题描述】:

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

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

到目前为止,我只找到了 2 个解决方案,但没有一个适合我。

第一个involved ATL usage and GDI+(我正在使用 Rust,我没有任何与 GDI+ 的绑定)。

第二个是基于LookupIconIdFromDirectoryEx() 和CreateIconFromResourceEx() 的使用。首先我调用LookupIconIdFromDirectoryEx() 来获取正确图标的偏移量,然后我尝试调用CreateIconFromResourceEx()(和CreateIconFromResource())来获取HICON,但在所有情况下我都会收到NULL 值作为结果,@ 987654337@ 虽然返回 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;
}

【问题讨论】:

  • 看起来您的代码中存在错误。除非您提供minimal reproducible example,否则我们无法为您提供帮助。
  • 修复代码而不是放弃
  • @IInspectable,我最初没有添加它,因为它与我链接的文章中的基本相同。但现在我更新了描述并附上了源代码。在我的情况下,它总是打印“FAIL 0”。
  • @DavidHeffernan 我很乐意修复它,我尝试了几种不同的方案,但它仍然不起作用,这就是我决定问的原因。我刚刚附上了我尝试使用的代码,不幸的是它总是失败。
  • 这不是minimal reproducible example。它缺少输入。如果无法上传图标,请在代码中添加一个静态数组,初始化为出现错误的最小图标。同时,您可以仔细检查,您的图标确实包含 32x32 图像。

标签: winapi icons gdi


【解决方案1】:

我找到了解决方案。实际上,经过一番研究,我发现我放在示例中的代码确实是正确的。

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

我做了几轮测试,根据format definition自己解析了每个图标文件的格式。然后我将实际偏移量与LookupIconIdFromDirectoryEx() 返回的值进行比较。

假设我们有 2 个图标:A 和 B。

在我的例子中,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. NOT FOUND(函数失败并返回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() 实现内部存在错误。

UPD。可在此处找到图标:A icon、B icon。

【讨论】:

  • 极不可能,Windows API 在这里有错误。您更有可能误解了结果。带有 PNG 图像的图标与带有 BMP 图像的图标非常不同。 Raymond Chen 有一个关于 ICO 文件格式的多部分系列。 The evolution of the ICO file format, part 4: PNG images 可能有助于理解您正在观察的结果。
  • @IInspectable,但结果显示即使我使用仅包含 BMP 图像的图标,它也无法正常工作。在我的回答中查看图标文件B,当我尝试搜索大于 128x128 像素的图标时,该功能失败。
  • 我最初询问了从ICO文件中加载HICON的方式,但似乎没有其他可靠的解决方案(至少我没有听到其他人的任何答案,我也是自己无法找到更好的解决方案)。对我有用的唯一方法是我描述的方法。然后我做了一个小的研究来找出它失败的原因。奇怪的是,我的回答收到了如此多的反对票,却没有解释原因。
  • 很有可能是您的输入错误,而不是 Windows API 中存在错误。但是,如果没有看到您的意见,我们无法在这里为您提供帮助。我不知道,为什么这个提议的答案也遭到 2 票反对,我也想看看为什么会这样。
  • @IInspectable 我会将该主题标记为已解决,因为我所做的研究已经(或多或少)回答了我的问题并帮助我了解如何正确解决它。但是,如果有任何其他更好的解决方案,我总是有兴趣看到它们(现在我将坚持到目前为止的结果)。我还更新了我的答案并附上了存档的链接(包含 2 个图标,里面有和没有 PNG),所以如果你有兴趣,可以看看它们。但无论如何,感谢您的建议和评论。
【解决方案2】:
CreateIconFromResourceEx((PBYTE)data + offset, 0, ...)

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

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

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

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

最好手动计算偏移量。看来您已经知道如何计算偏移值了。您可以根据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 的字节

【讨论】:

  • 感谢您的评论,没想到将近一年后会看到新的东西;)好吧,实际上正如我在原始帖子中提到的那样,无论0 值如何,该功能都失败了。至于bug,我仍然认为LookupIconIdFromDirectoryEx 包含bug,因为它在有效的资源数据上返回了错误的值。函数的输出取决于被请求的图标的大小(当图标的大小超过u16::max(),函数开始返回错误值/失败)。文档中未提及此类行为。
猜你喜欢
  • 2013-01-10
  • 2011-01-18
  • 2012-09-13
  • 2018-10-25
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多