1

In a Rust desktop application, some version of the window struct is always used, e.g., WNDCLASSW. When WNDCLASSW is defined, a class icon may be added through the struct member hIcon. The code excerpt below demonstrates how to include the icon stored in the file Icon.ico.

...
        let hicon: HICON = LoadImageW(
            0 as HINSTANCE, 
            wide_null("Icon.ico").as_ptr(), 
            IMAGE_ICON, 0, 0, LR_LOADFROMFILE
        ) as HICON;

        let hinstance = GetModuleHandleW(null_mut());

        let mut wc: WNDCLASSW = std::mem::zeroed();
        wc.lpfnWndProc = Some(window_proc);
        wc. hInstance = hinstance;
        wc.hIcon = hicon;
        wc.lpszClassName = name.as_ptr();
...

The icon file is loaded during the program execution and must be stored in the same folder as the exe file. If the icon file is missing, LoadImageW() returns a NULL handle. Setting hIcon to NULL is valid and causes the use of a standard system icon.

While this approach produces the required icon, the icon file is loaded during execution and must be delivered along with the exe file. This isn't an acceptable solution; the icon should be linked to the exe file and delivered within it.

How do I link an icon to a Rust Windows application and use it there?

I am aware of this solution, but it generates thousands of lines of errors and warnings during compilation and must be seen as outdated. This solution works, but it only adds the exe icon shown in Windows File Explorer, while the class icon (in the taskbar) is unchanged. Several other solutions for the exe icon may be found on the internet, but this is not what I'm looking for.

Kalle Svensson
  • 353
  • 1
  • 10
  • 2
    You can use the [embed-resource](https://crates.io/crates/embed-resource) crate to compile your .rc file(s) and have them linked into the binary. – IInspectable Jan 07 '23 at 09:01
  • @IInspectable: I'll experiment with this, but it opens new questions. I don't use any .rc file and don't know what it should contain. And if I succeed in linking the icon, how do I use it? You cannot use ``LoadImageW()`` any longer, so you have to set ``hIcon`` in some other way. A relevant example would help a lot. – Kalle Svensson Jan 07 '23 at 09:35
  • Your .rc file would need to contain an [ICON resource](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) definition statement for each icon you want to have embedded. It doesn't have to contain anything else. [`LoadImageW`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadimagew) can be used to load icon resources. You just need to construct a `PCWSTR` that holds the ID rather than a pointer, e.g. `PCWSTR(42 as _)` for a resource with ID 42. – IInspectable Jan 07 '23 at 10:00
  • @IInspectable: Thank you; I'll give it a try. But if you know how to solve it, why don't you add all the details and make it the answer? – Kalle Svensson Jan 07 '23 at 10:29

2 Answers2

4

The standard procedure to embed resources into an executable image on Windows is to author a resource file (.rc), have the resource compiler translate that into its binary representation, and pass that to the linker.

Since it's somewhat tedious to interact with the linker from Cargo, it's a fair bit easier to use an existing crate to deal with this. As you've discovered, there's winres (which appears to be a bit outdated), so I'll be using embed-resource here.

If you want to play along, start by creating a new binary crate

cargo new --bin embed_icon

Next, copy an icon of your choosing into the crate's root directory (I'm using "rust_lang_logo.ico" downloaded from here) and create the resource script (embed_icon.rc) in the same location:

1 ICON "rust_lang_logo.ico"

All this does is tell the resource compiler that it should look for an icon called "rust_lang_logo.ico", and assign it an ID of 1 when producing its binary output.

Of course we need a Cargo.toml as well:

[package]
name = "embed_icon"
version = "0.0.0"
edition = "2021"

[dependencies.windows]
version = "0.43.0"
features = [
    "Win32_Foundation",
    "Win32_UI_WindowsAndMessaging",
    "Win32_System_LibraryLoader",
]

[build-dependencies]
embed-resource = "1.8"

This is declaring the required windows features we'll be using, and imports embed-resource as a build dependency. What's left is src/main.rs

use windows::{
    core::{Result, PCWSTR},
    Win32::{
        System::LibraryLoader::GetModuleHandleW,
        UI::WindowsAndMessaging::{LoadImageW, IMAGE_ICON, LR_DEFAULTSIZE},
    },
};

fn main() -> Result<()> {
    let _icon = unsafe {
        LoadImageW(
            GetModuleHandleW(None)?,
            PCWSTR(1 as _), // Value must match the `nameID` in the .rc script
            IMAGE_ICON,
            0,
            0,
            LR_DEFAULTSIZE,
        )
    }?;

    Ok(())
}

This doesn't do much, other than trying to load the icon we just embedded into the binary. A cargo run later, and we have...

Error: Error { code: HRESULT(0x80070714), message: "The specified image file did not contain a resource section." }

and a binary that looks like this in File Explorer:

Screenshot with no embedded resource

The final step is to actually run embed-resource and have the icon linked into the executable image. A build script is required to do this. To add one create a file called "build.rs" in the crate's root directory with the following contents:

fn main() {
    embed_resource::compile("embed_icon.rc");
}

This neatly brings it all together. Running the executable is now successful, and the binary has a nice icon associated with it when displayed in File Explorer:

Screenshot with embedded resource


Note: The solution above uses the windows crate, which is designed for convenience and better safety. If you are using the winapi or windows-sys crate the core principles are still the same. The code in main.rs would have to be adjusted, though.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • Adding an *exe icon* is known, e.g. see the link in my question. I want to add a *class icon*. Your answer is knowledgeable but it doesn't solve the problem, it **does not produce the correct icon in the taskbar**. I'm trying to use your comment to set ``hIcon`` in ``WNDCLASSW``. – Kalle Svensson Jan 07 '23 at 12:25
  • @KalleSvensson That appears to be a different issue then (could be, that the .ico you are using doesn't contain an icon image for the required resolution). I updated my test code with a call to `MessageBox` and the icon shows up properly in the taskbar button as well. Regardless, this proposed answer addresses an issue with the `winres`-based solution: It puts you in control of assigning an ID to the icon resource, making it possible to refer to it in code. `winres` (from a quick look) doesn't provide a way to do that. Though I could be wrong. – IInspectable Jan 07 '23 at 13:02
  • The icon I'm using is good; it does pop up in the taskbar when I load it from a separate file, as shown in the question. Your test app just flashes and disappears, so I added a sleep after the call to ``LoadImageW()``. Now it stays longer. The icon in the taskbar is the ``cmd`` icon. Correct, it is a console app so it just pops up a console window and the corresponding icon. Is ``LoadImageW()`` enough to set the class icon? Don't you need to set ``hIcon`` in ``WNDCLASSW``? I like cooperating with you; chatting might be better. – Kalle Svensson Jan 07 '23 at 13:41
  • It works. I use winapi, and then you need the following: ``hIcon=LoadImageW( GetModuleHandleW(0 as LPCWSTR), MAKEINTRESOURCEW( 1 ), IMAGE_ICON, 0, 0, 0 ) as HICON;`` Could you add this to the answer? Many people will read and use it. Loading the image is not enough to change the *class icon*. – Kalle Svensson Jan 07 '23 at 18:36
  • And don’t forget to encode your `RC` file as UCS2, otherwise it will come out with unexpected errors – picrap Jul 21 '23 at 16:32
2

This text complements Answer 1 by IInspectable, adding information for the users of winapi.

Create the resource file <name>.rc and build.rs as described in Answer 1, and place these files along with the icon file <name>.ico in the project root directory. Add the build dependency to embed-resource in Cargo.toml. The application exe file built with these changes will embed the icon from the icon file.

To change the class icon that is shown in the taskbar, set the hIcon member in WNDCLASSW as follows:

hIcon = LoadImageW( 
    GetModuleHandleW(0 as LPCWSTR), // hInst; the .exe file
    MAKEINTRESOURCEW( 1 ),          // name; use the resource number in the .rc file
    IMAGE_ICON,                     // type
    0,                              // cx
    0,                              // cy
    0                               // fuLoad
    ) as HICON;
Kalle Svensson
  • 353
  • 1
  • 10
  • While I somehow forgot to follow up on [your request](https://stackoverflow.com/questions/75038925/how-to-link-an-icon-to-a-rust-windows-application/75046993#comment132427741_75039811), I'm curious as to whether the `fuLoad` value passed makes a difference. You're using `0` (aka `LR_DEFAULTCOLOR`) here. Does your code exhibit different behavior if you used `LR_DEFAULTSIZE` instead? – IInspectable Jan 17 '23 at 15:52
  • @IInspectable: this project is in stand by right now. It is about Rust, while I'm working with C++. But I'll check it upp and answer you. – Kalle Svensson Jan 17 '23 at 16:03
  • @IInspectable: I've tested LoadImageW with the parameter fuLoad set to (in a sequence): LR_DEFAULTSIZE, LR_LOADMAP3DCOLORS, LR_LOADTRANSPARENT, LR_MONOCHROME, LR_VGACOLOR. I could not see any change in the icon. – Kalle Svensson Jan 18 '23 at 22:40