从缓冲区加载 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 个图标:A
和 B
。
我的 A
图标包含 5 张图像,ICO 文件中的条目按以下顺序放置:
- 256x256 PNG
- 128x128 BMP
- 64x64 BMP
- 32x32 BMP
- 16x16 BMP
B
图标包含 7 张图片,它们按以下顺序放置:
- 16x16 BMP
- 32x32 BMP
- 48x48 BMP
- 64x64 BMP
- 128x128 BMP
- 256x256 BMP
每个图标的 LookupIconIdFromDirectoryEx()
结果如下。
图标A
:
- 86
- 9640
- 9640
- 9640
- 9640
图标B
:
- 102
- 1230
- 5494
- 15134
- 未找到(函数失败并返回
0
)
- 未找到(函数失败并返回
0
)
- 未找到(函数失败并返回
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()
实现中存在错误。
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
我只是想知道,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 个图标:A
和 B
。
我的 A
图标包含 5 张图像,ICO 文件中的条目按以下顺序放置:
- 256x256 PNG
- 128x128 BMP
- 64x64 BMP
- 32x32 BMP
- 16x16 BMP
B
图标包含 7 张图片,它们按以下顺序放置:
- 16x16 BMP
- 32x32 BMP
- 48x48 BMP
- 64x64 BMP
- 128x128 BMP
- 256x256 BMP
每个图标的 LookupIconIdFromDirectoryEx()
结果如下。
图标A
:
- 86
- 9640
- 9640
- 9640
- 9640
图标B
:
- 102
- 1230
- 5494
- 15134
- 未找到(函数失败并返回
0
) - 未找到(函数失败并返回
0
) - 未找到(函数失败并返回
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()
实现中存在错误。
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