【Delphi】操纵EXE文件中的主图标(MAINICON)

RT_GROUP_ICON 是 Windows 可执行文件中用于存储图标资源的重要结构,它本质上是一个图标目录,用于管理多个不同尺寸和颜色深度的图标图像。
参考:Winuser.h) (资源类型 - Win32 apps | Microsoft Learn
基本概念
RT_GROUP_ICON 资源类型的 ID 为 14(0x0E),它与 RT_ICON 资源配合使用:
- RT_GROUP_ICON:作为图标目录,描述了一组图标的元数据
- RT_ICON:存储实际的图标图像数据
这种结构类似于 ICO 文件,但有重要区别:RT_GROUP_ICON 不直接存储图像数据,而是通过资源 ID 引用对应的 RT_ICON 资源。
数据结构定义
GRPICONDIR 结构(图标目录头)
typedef struct {WORD idReserved; // 保留字段,必须为 0WORD idType; // 资源类型,图标为 1WORD idCount; // 图标数量GRPICONDIRENTRY idEntries[1]; // 图标条目数组
} GRPICONDIR, *LPGRPICONDIR;
GRPICONDIRENTRY 结构(图标条目)
typedef struct {BYTE bWidth; // 图标宽度(像素)BYTE bHeight; // 图标高度(像素)BYTE bColorCount; // 颜色数量(0 表示 >=8bpp)BYTE bReserved; // 保留字段,必须为 0WORD wPlanes; // 颜色平面数WORD wBitCount; // 每像素位数DWORD dwBytesInRes;// 资源数据大小(字节)WORD nID; // 对应的 RT_ICON 资源 ID
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY;
与 ICO 文件格式的区别
| 特征 | ICO 文件格式 | RT_GROUP_ICON 格式 |
|---|---|---|
| 最后一个字段 | dwImageOffset(文件偏移量) | nID(资源 ID) |
| 数据存储 | 直接包含图像数据 | 仅包含引用,图像数据在 RT_ICON 中 |
| 用途 | 磁盘文件存储 | EXE/DLL 资源存储 |
完整结构示例
假设一个包含 2 个图标的 RT_GROUP_ICON 资源:
[GRPICONDIR]
idReserved: 0x0000
idType: 0x0001
idCount: 0x0002[GRPICONDIRENTRY 1]
bWidth: 32
bHeight: 32
bColorCount: 0
bReserved: 0
wPlanes: 1
wBitCount: 32
dwBytesInRes: 4096
nID: 1[GRPICONDIRENTRY 2]
bWidth: 16
bHeight: 16
bColorCount: 0
bReserved: 0
wPlanes: 1
wBitCount: 32
dwBytesInRes: 1024
nID: 2
Delphi 中处理 RT_GROUP_ICON 的示例代码
读取 RT_GROUP_ICON 资源
usesWindows, SysUtils;typeTGRPICONDIR = recordidReserved: Word;idType: Word;idCount: Word;idEntries: array[0..0] of TGRPICONDIRENTRY;end;PGRPICONDIR = ^TGRPICONDIR;TGRPICONDIRENTRY = recordbWidth: Byte;bHeight: Byte;bColorCount: Byte;bReserved: Byte;wPlanes: Word;wBitCount: Word;dwBytesInRes: DWORD;nID: Word;end;PGRPICONDIRENTRY = ^TGRPICONDIRENTRY;procedure ReadGroupIcon(const ExeFileName: string);
varhModule: THandle;hResource: THandle;pResource: PByte;pGroupIcon: PGRPICONDIR;pEntry: PGRPICONDIRENTRY;i: Integer;
beginhModule := LoadLibraryEx(PChar(ExeFileName), 0, LOAD_LIBRARY_AS_DATAFILE);if hModule = 0 thenRaiseLastOSError;tryhResource := FindResource(hModule, MAKEINTRESOURCE(1), RT_GROUP_ICON);if hResource = 0 thenRaiseLastOSError;pResource := LockResource(LoadResource(hModule, hResource));if pResource = nil thenRaiseLastOSError;pGroupIcon := PGRPICONDIR(pResource);Writeln(Format('图标数量: %d', [pGroupIcon.idCount]));Writeln('资源类型: ', pGroupIcon.idType);pEntry := @pGroupIcon.idEntries[0];for i := 0 to pGroupIcon.idCount - 1 dobeginWriteln(Format('图标 %d:', [i + 1]));Writeln(Format(' 尺寸: %dx%d', [pEntry.bWidth, pEntry.bHeight]));Writeln(Format(' 颜色深度: %d bpp', [pEntry.wBitCount]));Writeln(Format(' 数据大小: %d 字节', [pEntry.dwBytesInRes]));Writeln(Format(' RT_ICON ID: %d', [pEntry.nID]));Writeln;Inc(pEntry);end;finallyFreeLibrary(hModule);end;
end;
更新 EXE 文件的图标资源
procedure UpdateExeIcon(const IconFilename, ExeFilename: string);
varhUpdate: THandle;IconStream: TFileStream;IconData: array of Byte;IconSize: DWORD;IconDir: TIconDir;IconEntry: TIconDirEntry;IconImage: TIconImage;i: Integer;
begin// 打开图标文件IconStream := TFileStream.Create(IconFilename, fmOpenRead or fmShareDenyNone);tryIconSize := IconStream.Size;SetLength(IconData, IconSize);IconStream.ReadBuffer(IconData[0], IconSize);// 解析 ICO 文件头Move(IconData[0], IconDir, SizeOf(TIconDir));if (IconDir.idReserved <> 0) or (IconDir.idType <> 1) thenraise Exception.Create('无效的 ICO 文件');// 开始更新 EXE 资源hUpdate := BeginUpdateResource(PChar(ExeFilename), False);if hUpdate = 0 thenRaiseLastOSError;try// 更新 RT_GROUP_ICONif not UpdateResource(hUpdate, RT_GROUP_ICON, MAKEINTRESOURCE(1), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), @IconData[0], IconSize) thenRaiseLastOSError;// 更新对应的 RT_ICON 资源for i := 0 to IconDir.idCount - 1 dobeginMove(IconData[SizeOf(TIconDir) + i * SizeOf(TIconDirEntry)], IconEntry, SizeOf(TIconDirEntry));// 读取图标图像数据Move(IconData[IconEntry.dwImageOffset], IconImage, SizeOf(TIconImage));// 更新 RT_ICON 资源if not UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(i + 1), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), @IconData[IconEntry.dwImageOffset], IconEntry.dwBytesInRes) thenRaiseLastOSError;end;// 完成更新if not EndUpdateResource(hUpdate, False) thenRaiseLastOSError;exceptEndUpdateResource(hUpdate, True);raise;end;finallyIconStream.Free;end;
end;
关键要点
- RT_GROUP_ICON 与 RT_ICON 的关系:前者是目录,后者是实际数据
- 资源 ID 的重要性:GRPICONDIRENTRY.nID 必须与对应的 RT_ICON 资源 ID 匹配
- 多语言支持:可以为不同语言创建不同的 RT_GROUP_ICON 资源
- 兼容性:从 Windows Vista 开始支持 PNG 压缩的图标数据
- 资源更新:更新图标时需要同时更新 RT_GROUP_ICON 和对应的 RT_ICON 资源
理解 RT_GROUP_ICON 格式对于处理 Windows 应用程序图标、资源编辑和 EXE 文件修改等任务非常重要。
