我正在尝试对 Windows 可执行文件的图标进行资源更新,我快到了,我做错了什么?

I'm trying to do a resource update on the icons of a Windows executable and I'm nearly there, what am I doing wrong?

这是我的代码的新版本。现在更接近了,如果我查看 Resource Hacker 中的更新版本,它告诉我该组有九个图标,这是真的,它们有 1680 万种颜色,这是真的,它们都是 16 x 16 ,这不是真的,而且它实际上无法向我展示它们的样子,这很烦人。此外,如果这对任何人都有意义的话,它们都有一个序数名称 150。

procedure TForm1.Button1Click(Sender: TObject);
var   vResHandle: THandle;
      MyIcon: TMemoryStream;
begin
  // Get the icon.
  MyIcon := TMemoryStream.Create;
  MyIcon.LoadFromFile('icon.ico');
  // Set the position in the memory stream to the start.
  MyIcon.Seek(0, soFromBeginning);

  // Get the handle.
  vResHandle := BeginUpdateResource('exec.exe', False);
  if vResHandle=0 then
    raise Exception.Create('System giving error message: '
                           + SysErrorMessage(GetLastError));
    try
    // Change the icon.
    if not UpdateResource(vResHandle
                          , RT_GROUP_ICON
                          , PChar('MAINICON')
                          , LANG_NEUTRAL
                          , MyIcon.Memory
                          , MyIcon.Size)
    then
      raise Exception.Create('System giving error message: '
                             + SysErrorMessage(GetLastError));
    finally
    EndUpdateResource(vResHandle, False);
    end;
  MyIcon.Free;
end;  

            

它是如何工作的简短版本:所以。在您尝试使用资源更新将任何数据放入 .exe 文件之前,您必须确保它适合。图标文件很难。在这种特殊情况下,我需要修改 .ico 文件的结构并将其拆分为不同的部分,并分别对每个部分进行资源更新。我没有那样做。我就像有人试图将七指手放入五指手套的一根手指中。

代码中解释了它的工作原理,但必须在此处解释它对 Windows 的确切影响。

(1) 虽然应用程序图标(在你的主窗体左上角)可以设置为与程序的主图标完全不同,但看起来它被覆盖以与主图标一致更新后的图标。 99% 的时间这正是您想要的。如果它不是您想要的,您将不得不从这里拿走它。

(2) 文件资源管理器缓存这些内容非常困难,除非您重新启动资源管理器,否则您看不到图标在那里的显示方式有任何变化。这对我来说很好,如果这对您来说是个问题,那么您将不得不自己解决,抱歉。

(3) 这不是对“如何在 运行 时更改基于 Pascal 的应用程序的工具栏图标?”这个常见问题的答案。因为您无法对正在执行的可执行文件进行资源更新,无论是您自己的还是其他人。

(4) 如果您打算在 Pascal 中使用它,那么您需要将 Windows 添加到您的 uses 语句中。如果你打算在 Pascal 以外的任何语言中使用它,但你仍在使用 Windows,那么它会很容易翻译,因为它基本上告诉 Windows OS 到做一些事情,但你必须找出哪个库或任何允许你做的东西以及它希望你使用什么语法。

(5) 如果您想知道如何反过来做这件事并从可执行文件中提取 .ico 文件,那么这在理论上当然是可能的,并且比我更聪明的人已经完成了确实是用 Pascal 完成的。 (例如下载 Resource Hacker。)但是你不能仅仅通过逆向我的代码来做到这一点,你的方式有障碍。这样做 Windows 已经为我建立了这样做的设施。换个方式好像不行。

procedure TForm1.Button1Click(Sender: TObject);
var   vResHandle: THandle;
      MyIcon: TMemoryStream;
      i,j: integer;
      s: string;
      ImageCount: Word;
      ImageSize: DWord;
      ab, m: TMemoryStream;
 
const HeaderSize = 6;
      IcoEntrySize = 16;
      ResEntrySize = 14;
 
begin
 
// Short explanation. An icon file consists of (a) a six-byte header which
// includes among other things information about how many icons are in
// the file; (b) sixteen bytes of metadata for each icon; (c) the icons.
 
// But that's how icons are stored as files. As executable resources,
// Windows considers that (a) and (b) are one resource but (c) is a different
// resource, indeed one resource for each image, so we have to split the icon
// file up and do several resource updates.
 
// It also requires only fourteen bytes of metadata per entry: instead of the
// last parameter being a double word referring to the position of the image
// in memory, it's a single word conferring an ID.
 
// Initialize stuff
MyIcon := TMemoryStream.Create;
ab := TMemoryStream.Create;
m := TMemoryStream.Create;
 
// Get the icon
MyIcon.LoadFromFile('icon.ico');
 
// Get the handle for the resource update..
vResHandle := BeginUpdateResource('test.exe', False);
 
// We skip forward in the memory stream to where Windows keeps the image count and read it.
MyIcon.Seek(4,soFromBeginning);
ImageCount:=MyIcon.ReadWord;
 
// Go back to the beginning ...
MyIcon.Seek(0,soFromBeginning);
 
// We read the directory information into ab, modifying its format as we do so.
 
for j:=1 to HeaderSize do ab.WriteByte(MyIcon.ReadByte);
    for i:=1 to ImageCount do
        begin
        for j:=1 to IcoEntrySize - 4 do ab.WriteByte(MyIcon.ReadByte);
        MyIcon.ReadDWord;  // To skip over it.
        ab.WriteWord(i);
        end;
 
// Update the icon directory with ab, which is now in the correct format.
 
UpdateResource(vResHandle
                      , RT_GROUP_ICON
                      , PChar('MAINICON')
                      , LANG_NEUTRAL
                      , ab.Memory
                      , ab.Size);
 
// Now the size of each icon is contained as a double word in the directory
// entries for each item, so we use that to cut the remainder of the memory
// stream into chunks and update them one at a time.
 
for i := 1 to ImageCount do
    begin
    m := TMemoryStream.Create;
    ab.Seek(HeaderSize+(i-1)*ResEntrySize+8,soFromBeginning);
    ImageSize:=ab.ReadDWord;
    for j:=1 to ImageSize do m.WriteByte(MyIcon.ReadByte);
    UpdateResource(vResHandle
                  , RT_ICON
                  , MAKEINTRESOURCE(i)
                  , LANG_NEUTRAL
                  , m.Memory
                  , m.Size);
    m.Free;
    end;
 
EndUpDateResource(vResHandle,False);
MyIcon.Free;
ab.Free;
end;