需求:
客户需要定制程序的图标, 期望不需要重新编译也能修改图标.
方法:直接更新exe图标资源.
官网:添加、删除和替换资源 - Win32 apps | Microsoft Learn
例子: 使用资源 - Win32 apps | Microsoft Learn
资源类型: Winuser.h) (资源类型 - Win32 apps | Microsoft Learn
解析资源组种图标或游标组件的组成:NEWHEADER+NEWHEADER.ResCount*RESDIR
typedef struct {
WORD Reserved;//保留;必须为零。
WORD ResType;//RES_ICON=1图标资源类型。RES_CURSOR=2游标资源类型。
WORD ResCount;//资源组中的图标或游标组件数。
} NEWHEADER, *PNEWHEADER;
typedef struct {
ICONRESDIR Icon;//所指示图标的宽度、高度和颜色计数。
CURSORDIR Cursor;//所指示光标的宽度和高度。
WORD Planes;//图标或光标位图中的颜色平面数。
WORD BitCount;//图标或光标位图中每像素的位数。
DWORD BytesInRes;//资源的大小(以字节为单位)。
WORD IconCursorId;//具有唯一序号标识符的图标或光标。
} RESDIR;//请注意, RESDIR 结构由 ICONRESDIR 结构或 CURSORDIR 结构组成,也就是说要么是图标,要么是光标,只能存在一个
typedef struct {
BYTE Width;//图标的宽度(以像素为单位)。 可接受的值为 16、32 和 64,128。
BYTE Height;//图标的高度(以像素为单位)。 可接受的值为 16、32 和 64,128。
BYTE ColorCount;//图标中的颜色数。 可接受的值为 2、8 和 16。
BYTE reserved;//保留;必须设置为与图标文件标头中保留字段的值相同的值。
} ICONRESDIR;
typedef struct {
WORD Width;//光标的宽度(以像素为单位)。 可接受的值为 16、32 和 64。
WORD Height;//光标的高度(以像素为单位)。 可接受的值为 16、32 和 64。
} CURSORDIR;
RESDIR 结构 - Win32 apps | Microsoft Learn
资源结构 (菜单和其他资源) - Win32 apps | Microsoft Learn
由此我们可以分析出ICO的结构是:NEWHEADER+NEWHEADER.ResCount*ICONRESDIR+图片数据
typedef struct {
WORD Reserved;//保留;必须为零。
WORD ResType;//RES_ICON=1图标资源类型。RES_CURSOR=2游标资源类型。
WORD ResCount;//资源组中的图标或游标组件数。
} NEWHEADER, *PNEWHEADER;
typedef struct {
BYTE Width;//图标的宽度(以像素为单位)。 可接受的值为 16、32 和 64,128。
BYTE Height;//图标的高度(以像素为单位)。 可接受的值为 16、32 和 64,128。
BYTE ColorCount;//图标中的颜色数。 可接受的值为 2、8 和 16。
BYTE reserved;//保留;必须设置为与图标文件标头中保留字段的值相同的值。
WORD Planes;//图标或光标位图中的颜色平面数。
WORD BitCount;//图标或光标位图中每像素的位数。
DWORD BytesInRes;//资源的大小(以字节为单位)。
DWORD IconCursorId;//经过测试,发现这是ico中每副图像数据(image)起点位置的偏移,并且是DWORD而不是WORD
} ICONRESDIR;//16字节
#include <Windows.h>
#include <winbase.h>
//icoFilePath:修改的图标
//exeFilePath:修改的程序exe
bool parseIco(QString icoFilePath, std::map<int, ICONRESDIR> &outDataMap, QString exeFilePath)
{
char szIco[1024] = {0};
icoFilePath.replace("/", "\\");
memcpy(szIco, icoFilePath.toLocal8Bit().data(), icoFilePath.toLocal8Bit().size());
// open the icon file
// 计算需要的宽字符缓冲区大小
int nChars = MultiByteToWideChar(CP_ACP, 0, szIco, -1, NULL, 0);
// 分配足够的空间给宽字符缓冲区
wchar_t *szPathW = new wchar_t[nChars];
if (!szPathW) {
// 处理内存分配失败
return false;
}
// 进行转换
MultiByteToWideChar(CP_ACP, 0, szIco, -1, szPathW, nChars);
// 现在可以使用 szPathW 作为 LPCWSTR 参数
HANDLE hFile = CreateFileW(szPathW, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
delete[] szPathW;
qCritical() << "Failed open Icon File! err = " << GetLastError();
return false;
}
// 使用完毕后,记得释放内存
delete[] szPathW;
// get the file size
DWORD dwFileSize = GetFileSize(hFile, NULL);
unsigned char *filemem = new unsigned char[dwFileSize];
memset(filemem, 0, dwFileSize);
// read file to memory
DWORD dwBytesRead(0);
ReadFile(hFile, filemem, dwFileSize, &dwBytesRead, NULL);
CloseHandle(hFile);
NEWHEADER *iconHeader = (NEWHEADER *)(filemem);
auto *outheader = filemem + sizeof(NEWHEADER);
outDataMap.clear();
HANDLE hUpdateRes = nullptr; // update resource handle
if (!exeFilePath.isEmpty())
{
char szExeFile[1024] = {0};
memcpy(szExeFile, exeFilePath.toStdString().c_str(), exeFilePath.toStdString().size());
hUpdateRes = BeginUpdateResource(TEXT(szExeFile), FALSE);
if (hUpdateRes == NULL)
{
qCritical() << "BeginUpdateResource File!";
return false;
}
}
for (int i = 0; i < iconHeader->ResCount; ++i)
{
auto outheader_ = (ICONRESDIR *)(outheader + (sizeof(ICONRESDIR) * i));
outDataMap[i] = *outheader_;
if (hUpdateRes)
{
BOOL result;
result = UpdateResource(hUpdateRes, // update resource handle
RT_ICON, // change resource
MAKEINTRESOURCE(i + 1), // id == 0 是 RT_GROUP_ICON 的id,所有全部+1
MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), // neutral language
(LPVOID)(filemem + outheader_->IconCursorId), // ptr to resource info
outheader_->BytesInRes);
if (result == FALSE)
{
qCritical() << "UpdateResource File!";
break;
}
}
}
if (hUpdateRes)
{
// Write changes to FOOT.EXE and then close it.
if (!EndUpdateResource(hUpdateRes, FALSE))
{
qCritical() << "EndUpdateResource File!";
return false;
}
}
delete[] filemem;
return true;
}
//调用
std::map<int, ICONRESDIR> outDataMap;
parseIco(".ico",outDataMap,".exe");
修改后如果图标没有立刻发生变化那么需要清理图标缓存:批处理.bat
rem 关闭Windows外壳程序explorer
taskkill /f /im explorer.exe
rem 清理系统图标缓存数据库
attrib -h -s -r "%userprofile%\AppData\Local\IconCache.db"
del /f "%userprofile%\AppData\Local\IconCache.db"
attrib /s /d -h -s -r "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\*"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_32.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_96.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_102.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_256.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_1024.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_idx.db"
del /f "%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_sr.db"
rem 清理 系统托盘记忆的图标
echo y|reg delete "HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" /v IconStreams
echo y|reg delete "HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" /v PastIconsStream
rem 重启Windows外壳程序explorer
start explorer
ok, 完美解决.