0

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;  

            
Ken White
  • 123,280
  • 14
  • 225
  • 444
T.G. Grace
  • 35
  • 5
  • So, do you get either of the exceptions or what? Also, in https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-updateresourcea, the comments on the `lpData` parameter state "Note that this is the raw binary data to be stored in the file indicated by hUpdate, not the data provided by LoadIcon, LoadString, or other resource-specific load functions." Also see e.g. https://stackoverflow.com/questions/36140564/create-and-load-resources-in-delphi – MartynA May 01 '21 at 11:52
  • @MartynA, thanks. No, Windows is surprisingly accommodating and has let me do all sorts of dumb wrong stuff to get even this far without throwing a single error message. I like error messages, they tell me what I'm doing wrong. I did notice that comment on lpData, that's why I thought this would be the right thing. – T.G. Grace May 01 '21 at 13:11
  • @MartynA, the TMemoryStream class in OP's code in your link looks like what I need. Even though his code didn't work for him, it'll work for me because my application doesn't need to change its _own_ icons. I'll try it out and let you know if it works, update my OP with the solution. Thank you! – T.G. Grace May 01 '21 at 13:20
  • @MartynA, you can see from my revised post that it didn't quite work. I'm puzzled, it looks like it should work. – T.G. Grace May 01 '21 at 15:33
  • I have found the thing I needed to know. I need to separate the icon file into two parts and then put them in two places. How the heck wasn't I getting any error messages when I tried to do this instead? – T.G. Grace May 01 '21 at 15:54
  • Glad you are making progress. But I really think you should reinstate your original code and text as at the moment it isn't really a proper SO question. – MartynA May 01 '21 at 16:41
  • I've rolled back your edit. You cannot deface your post once you've written it here. If you don't want it here any longer, you can delete it using the link below the tags. You **cannot** remove the content per the terms of use of this site - once you've posted here, the content is the property of the site, not yours. – Ken White May 01 '21 at 18:44
  • @KenWhite, I'm sorry, I didn't know that. What counts as defacing? If I posted new versions at the bottom would that be OK? In the meantime I'll start a new thread ... I really have very nearly got it now, and can ask a really specific question about what's going wrong. – T.G. Grace May 02 '21 at 19:22
  • *defacing* is removing all of the content and replacing it with things like *Edit: I believe I'm on the right track now. I may not have enough time to implement it today but I will post it when I'm done. Love y'all.*. The proper things you can do are a) if you found a solution and want to share it, write an answer in the space provided below for that purpose; b) if you don't want the post here any longer, delete it; and c) if you don't want to do wither of the previous options, leave the post alone and hope that someone will answer it for you. – Ken White May 02 '21 at 19:28
  • @KenWhite, I'm still not sure I understand the rules of this place. I now have a piece of working code. If I leave my post up, then eventually someone trying to solve this same problem will use google, find this thread, and want to see the actual code, not just as much of the solution as I can explain in 600 characters of non-code. What is the approved appropriate way for me to let them see it? Please forgive my ignorance, I'm ... out of my millieu would be a kind way of putting it I guess. – T.G. Grace May 03 '21 at 11:07
  • If you have working code you want to share, do so by **writing an answer in the space below provided for that purpose**. It's got a heading above it that says **Your Answer** and a large text area for you to use to write that answer. It's about three inchoes down the page from where my comment will appear. **That's** where answers to questions (which would include your working solution) belong. A question is a question (problem), and an answer is a solution to that problem. They are distinct and separate entities. – Ken White May 03 '21 at 12:06
  • @KenWhite, thanks. Sorry, remember I'm a complete n00b round here, there is no "space below" unless you press the button first. When you said "space" I was looking for a box to type in, so I thought you meant this thing I'm typing in right now. I really am new here. If you want to avoid these mix-ups in the future, there is no "space below" other than this one. There's just a button which can conjure up another space. Yes, apparently I'm an idiot, but also you gave unclear instructions so you shouldn't be snippy. Thank you again for the actual advice. – T.G. Grace May 04 '21 at 21:08
  • Space below - look down on this page, about three inches below this comment. You'll see a very large edit area, with a heading above it that says **Your Answer**. It's right below the *Know someone who can answer? Share a link* text. It has a toolbar right below the **Your Answer** heading with icons on it. Right below that edit area, there's a button labeled **Post your answer**. That's the area where answers should be posted. Not in your question, not in this comment area, but in the **Your Answer** area. It's on every single question page, including this one. Scroll down. – Ken White May 04 '21 at 23:23
  • And I'm not being *snippy*. I'm trying to help you understand how the site works. It wouldn't hurt you to look down the page further than the *Add a comment* link to see what else is on the page when I said *a large text area for you to use* and *it's about three inches down the page from where my comment will appear*, which I said before your last comment and repeated again just after it. – Ken White May 04 '21 at 23:27

1 Answers1

0

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;
T.G. Grace
  • 35
  • 5