简体   繁体   中英

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?

Here's a fresh version of my code. It is now even closer, if I look at the updated version in Resource Hacker it tells me that the group has nine icons, which is true, that they're 16.8 million color, which is true, that they're all 16 x 16, which is not true, and that it can't actually show me what they look like, which is annoying. Also that they all have an ordinal name of 150 if that means anything to anyone.

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;  

            

Short version of how it works: So. Before you try to put any bit of data into an.exe file using a resource update you must be sure it will fit. Icon files are difficult. In this particular case I needed to modify the structure of the.ico file and split it into different pieces and do a resource update separately on each. I didn't do that. I was like someone trying to fit a seven-fingered hand into one finger of a five-fingered glove.

How the thing works is explained in the code but what exact effect it has on Windows must be explained up here.

(1) Although the application icon (in the top left corner of your main form) can be set to be completely different from the main icon for the program, it seems like it's overwritten to be in line with the main icon once you do the update. 99% of the time this would be exactly what you want. If it isn't what you want you'll have to take it from here.

(2) File Explorer caches this stuff so hard that you won't see any change in how the icon's displayed there unless you restart Explorer. This is fine for my purposes, if it's a problem for you then again you'll have to solve it yourself, sorry.

(3) This is NOT an answer to that frequently-asked question, "How do I change the toolbar icon of my Pascal-based application while it's running?" Because you can't do a resource update on an executable that's being executed, whether your own or another.

(4) If you're going to use this in Pascal, then you're going to need to add Windows to your uses statement. If you're going to use this in any language in other than Pascal but you're still using Windows, then it will translate kind of easily because it's basically telling the Windows OS to do stuff, but you'll have to find out which library or whatever lets you do that and what syntax it wants you to use.

(5) If you're wondering about how to do the thing the other way round and extract an.ico file from an executable file, then this is of course theoretically possible and has been done by cleverer people then me and indeed done in Pascal. (Download Resource Hacker for an example.) But you can't just do this by reversing my code as it were, there are obstacles in your way. Doing it this way Windows has built in facilities for me to do this. Doing it the other way it seems like it doesn't.

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM