【问题标题】: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 个图标: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. 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 iconB 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
相关资源
最近更新 更多