【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 资源。

RT_GROUP_ICON (ID=IDI_MAIN)
├─ GRPICONDIR
│  ├─ idReserved=0
│  ├─ idType=1
│  ├─ idCount=2 (2 个图标)
│  └─ idEntries[2]
│     ├─ 第 1 项:bWidth=16, bHeight=16, wBitCount=32, nID=101 (关联 RT_ICON 101)
│     └─ 第 2 项:bWidth=32, bHeight=32, wBitCount=32, nID=102 (关联 RT_ICON 102)
└─ 
RT_ICON (ID=101) → 16×16 32 位图标数据(ICONIMAGE 结构)
RT_ICON (ID=102) → 32×32 32 位图标数据(ICONIMAGE 结构)

数据结构定义

GRPICONDIR 结构(图标目录头)

typedef struct {
  WORD  idReserved;   // 保留字段,必须为 0
  WORD  idType;       // 资源类型,图标为 1
  WORD  idCount;      // 图标数量
  GRPICONDIRENTRY idEntries[1]; // 图标条目数组
} GRPICONDIR, *LPGRPICONDIR;

GRPICONDIRENTRY 结构(图标条目)

typedef struct {
  BYTE   bWidth;      // 图标宽度(像素)
  BYTE   bHeight;     // 图标高度(像素)
  BYTE   bColorCount; // 颜色数量(0 表示 >=8bpp)
  BYTE   bReserved;   // 保留字段,必须为 0
  WORD   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 资源

uses
  Windows, SysUtils;

type
  TGRPICONDIR = record
    idReserved: Word;
    idType: Word;
    idCount: Word;
    idEntries: array[0..0] of TGRPICONDIRENTRY;
  end;
  PGRPICONDIR = ^TGRPICONDIR;

  TGRPICONDIRENTRY = record
    bWidth: Byte;
    bHeight: Byte;
    bColorCount: Byte;
    bReserved: Byte;
    wPlanes: Word;
    wBitCount: Word;
    dwBytesInRes: DWORD;
    nID: Word;
  end;
  PGRPICONDIRENTRY = ^TGRPICONDIRENTRY;

procedure ReadGroupIcon(const ExeFileName: string);
var
  hModule: THandle;
  hResource: THandle;
  pResource: PByte;
  pGroupIcon: PGRPICONDIR;
  pEntry: PGRPICONDIRENTRY;
  i: Integer;
begin
  hModule := LoadLibraryEx(PChar(ExeFileName), 0, LOAD_LIBRARY_AS_DATAFILE);
  if hModule = 0 then
    RaiseLastOSError;
    
  try
    hResource := FindResource(hModule, MAKEINTRESOURCE(1), RT_GROUP_ICON);
    if hResource = 0 then
      RaiseLastOSError;
      
    pResource := LockResource(LoadResource(hModule, hResource));
    if pResource = nil then
      RaiseLastOSError;
      
    pGroupIcon := PGRPICONDIR(pResource);
    
    Writeln(Format('图标数量: %d', [pGroupIcon.idCount]));
    Writeln('资源类型: ', pGroupIcon.idType);
    
    pEntry := @pGroupIcon.idEntries[0];
    for i := 0 to pGroupIcon.idCount - 1 do
    begin
      Writeln(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;
  finally
    FreeLibrary(hModule);
  end;
end;

更新 EXE 文件的图标资源

procedure UpdateExeIcon(const IconFilename, ExeFilename: string);
var
  hUpdate: 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);
  try
    IconSize := 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) then
      raise Exception.Create('无效的 ICO 文件');
    
    // 开始更新 EXE 资源
    hUpdate := BeginUpdateResource(PChar(ExeFilename), False);
    if hUpdate = 0 then
      RaiseLastOSError;
      
    try
      // 更新 RT_GROUP_ICON
      if not UpdateResource(hUpdate, RT_GROUP_ICON, MAKEINTRESOURCE(1), 
        MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), @IconData[0], IconSize) then
        RaiseLastOSError;
      
      // 更新对应的 RT_ICON 资源
      for i := 0 to IconDir.idCount - 1 do
      begin
        Move(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) then
          RaiseLastOSError;
      end;
      
      // 完成更新
      if not EndUpdateResource(hUpdate, False) then
        RaiseLastOSError;
    except
      EndUpdateResource(hUpdate, True);
      raise;
    end;
  finally
    IconStream.Free;
  end;
end;

关键要点

  1. RT_GROUP_ICON 与 RT_ICON 的关系:前者是目录,后者是实际数据
  2. 资源 ID 的重要性:GRPICONDIRENTRY.nID 必须与对应的 RT_ICON 资源 ID 匹配
  3. 多语言支持:可以为不同语言创建不同的 RT_GROUP_ICON 资源
  4. 兼容性:从 Windows Vista 开始支持 PNG 压缩的图标数据
  5. 资源更新:更新图标时需要同时更新 RT_GROUP_ICON 和对应的 RT_ICON 资源

理解 RT_GROUP_ICON 格式对于处理 Windows 应用程序图标、资源编辑和 EXE 文件修改等任务非常重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海纳老吴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值