对于EXE文件,SHGetFileInfo有时会错误地返回系统图像列表中索引为2 的通用应用程序图标,并尝试了多种标志位
我有一个ListView,在详细信息视图中为文件显示小图标,包括EXE文件。
ListView的系统图像列表首先通过对一个虚拟文件名调用 SHGetFileInfo 来初始化,然后通过 LVM_SETIMAGELIST 搭配 LVSIL_SMALL 将其设入ListView。
SHFILEINFO sfi;
ZeroMemory(&sfi, sizeof(sfi));
HIMAGELIST hSystemImageList = (HIMAGELIST)SHGetFileInfo(L"NONE",
FILE_ATTRIBUTE_NORMAL,
&sfi,
sizeof(SHFILEINFO),
SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_SMALLICON
);
SendMessage((HWND)hWndListView, LVM_SETIMAGELIST, (WPARAM)LVSIL_SMALL, (LPARAM)hImageList);
当我用 SHGetFileInfo 搭配 SHGFI_SYSICONINDEX 以及 SHGFI_ICON、SHGFI_SMALLICON 的各种组合调用时,一些EXE的图标索引会正确获取,但其他的总是显示通用应用程序图标的索引 2。已经尝试了多种标志和选项。首先,给出正在发生的情况的示意:这是调试器中正确与不正确结果的一个示例:
原始:
SHGetFileInfo(fullpath.c_str(), 0,
&shFileInfo,
sizeof(shFileInfo), SHGFI_TYPENAME | SHGFI_ICON | SHGFI_SMALLICON | SHGFI_SYSICONINDEX);
没有 SHGFI_ICON、SHGFI_SMALLICON,或两者都没有:
SHGetFileInfo(fullpath.c_str(), 0,
&shFileInfo, sizeof(shFileInfo), SHGFI_TYPENAME | SHGFI_ICON | SHGFI_SYSICONINDEX);
SHGetFileInfo(fullpath.c_str(), 0,
&shFileInfo, sizeof(shFileInfo), SHGFI_TYPENAME | SHGFI_SMALLICON | SHGFI_SYSICONINDEX);
SHGetFileInfo(fullpath.c_str(), 0,
&shFileInfo, sizeof(shFileInfo), SHGFI_TYPENAME | SHGFI_SYSICONINDEX);
带有 SHGFI_OVERLAYINDEX 和 LINK_OVERLAY:
SHGetFileInfo(fullpath.c_str(), 0,
&shFileInfo, sizeof(shFileInfo), SHGFI_TYPENAME |
SHGFI_ICON | SHGFI_SYSICONINDEX | SHGFI_OVERLAYINDEX | SHGFI_LINKOVERLAY);
另外还有 ADD_OVERLAYS:
SHGetFileInfo(fullpath.c_str(), 0,
&shFileInfo, sizeof(shFileInfo), SHGFI_TYPENAME | SHGFI_ICON | SHGFI_SYSICONINDEX |
SHGFI_OVERLAYINDEX | SHGFI_LINKOVERLAY | SHGFI_ADDOVERLAYS);
考虑到有些文章认为这可能相关,尝试调用 CoInitialize() / CoUninitialize():
CoInitialize(NULL);
SHGetFileInfo(fullpath.c_str(), 0,
&shFileInfo, sizeof(shFileInfo),
SHGFI_TYPENAME | SHGFI_ICON | SHGFI_SMALLICON | SHGFI_SYSICONINDEX);
CoUninitialize();
该项目的平台是64位。一些文章认为这可能是由于某种32位/64位差异所致。

就此问题的处理而言,我可以尝试一种变通方法,比如 ExtractIconEx(fullpath.c_str(), 0, &hIconLarge, &hIconSmall, 1),但对我来说这会很糟,因为ListView不接受 HICON,它只接受图像列表的索引;且它也只适用于一个图像列表,而不是多个。这意味着我得维护一个自定义的ImageList,在其中复制/重复大多数非EXE的图标,同时添加我自己手动提取的图标。这会很混乱,影响性能,所以我真的需要解决这个问题。有什么建议吗?
注:路径已验证无误,拼接时使用反斜杠(\\ -> \)
解决方案
基于其中的一些建议,以下方案现在对我来说可行:
- 首先,如果不是EXE,就按正常方式填充系统图像列表图标和ListView(这部分从来没有问题)
- 在此之后单独进行:
- 将当前的系统图像列表复制到一个自定义ImageList,并获取当前大小,为后续向自定义EXE追加添加图标做准备。并将ListView设置为新的自定义ImageList。
HIMAGELIST sysImageList = getSysImageList(); // Assume this function returns Sys Img List
HIMAGELIST customImageList = NULL;
//...
if (customImageList) {
ImageList_Destroy(customImageList); // First destroy prior customImageList if exists
}
customImageList = ImageList_Duplicate(sysImageList); // Duplicate current System ImageList
int currCustomImageListSize = ImageList_GetImageCount(customImageList); // Get curr count
// Update ListView to use new Custom Image List immediately
ListView_SetImageList(hWndListView, customImageList, LVSIL_SMALL);
- 遍历EXE文件,执行更完整的
ExtractIconEx,它不会失败,并在自定义图像列表中按自增后的大小索引添加真实图标到自定义图像列表中
for (FileItem fileItem : fileItems) {
if (fileItem.extension == L"exe") {
HICON hSmallIcon = NULL;
ExtractIconEx(fileItem.fullpath.c_str(), 0, NULL, &hSmallIcon, 1);
ImageList_AddIcon(customImageList, hSmallIcon); // Add real EXE icon to Custom ImageList
// Clean up hSmallIcon
if (hSmallIcon) {
DestroyIcon(hSmallIcon);
}
// Icon Index will be currCustomImageListSize++
// In this example, set Img List Index on FileItem object
fileItem.iconIndex = currCustomImageListSize++;
// Update ListView's item via LVITEM
LVITEM lvItem = ... // Set up LVItem for update, based on fileItem
ListView_SetItem(hWndListView, &lvItem);
}
}
性能也不算差,ImageList_Duplicate 和 ExtractIconEx 各自大约只需要 ~4ms,而且我本来就是在后台线程中进行这些EXE的填充。
