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.
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:
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:
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.