DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,503 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Embed a Windows manifest in your Rust program
Carey
Carey

Posted on

Embed a Windows manifest in your Rust program

If you’re distributing a standalone program for Windows, you should include an XML application manifest, but Cargo and Rust don’t provide an easy way to do this. In this post I’ll show you how you can do this yourself.

I’m using the manifest below, which unlocks compatibility with Windows versions 7 to 11, so that Windows doesn’t lie to your program that it’s running on Vista; sets the code page to UTF-8 so that you can pass Rust strings directly to API’s like MessageBoxA; and enables long path names if you configure them in the registry, letting your current directory be very long.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
        </application>
    </compatibility>
    <asmv3:application>
        <asmv3:windowsSettings>
            <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
            <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
        </asmv3:windowsSettings>
    </asmv3:application>
    <asmv3:trustInfo>
        <asmv3:security>
            <asmv3:requestedPrivileges>
                <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
            </asmv3:requestedPrivileges>
        </asmv3:security>
    </asmv3:trustInfo>
</assembly>
Enter fullscreen mode Exit fullscreen mode

The Visual Studio Build Tools way

If you only ever compile programs for Windows, on Windows, using the Visual Studio Build Tools, you can use a build script to add your Windows manifest by passing a couple of extra arguments to LINK.EXE:

use std::{env, path::Path};

fn main() {
    if env::var("TARGET").expect("target").ends_with("windows-msvc") {
        let manifest = Path::new("hello.exe.manifest").canonicalize().unwrap();
        println!("cargo:rustc-link-arg-bins=/MANIFEST:EMBED");
        println!("cargo:rustc-link-arg-bins=/MANIFESTINPUT:{}", manifest.display());
        println!("cargo:rerun-if-changed=hello.exe.manifest");
    }
    println!("cargo:rerun-if-changed=build.rs");
}
Enter fullscreen mode Exit fullscreen mode

This is what rustc does in release 1.63.

The β€œhold my beer” way

If you are compiling or cross-compiling with the MinGW tools, you can embed the manifest resource using inline assembly:

#![windows_subsystem = "windows"]

use std::ffi::c_void;
use std::ptr::null;

#[link(name = "kernel32")]
extern "system" {
    fn GetVersion() -> u32;
}

#[link(name = "user32")]
extern "system" {
    fn MessageBoxA(hwnd: *const c_void, lpText: *const u8, lpCaption: *const u8, uType: u32) -> i32;
}

fn main() {
    let version = unsafe { GetVersion() };
    let major_version = version & 0x0f;
    let minor_version = (version & 0xf0) >> 8;
    let build = if version < 0x80000000 { version >> 16 } else { 0 };
    let message = format!(
        "It’s raining 🐈s and πŸ•s.\n\nI think this is Windows {}.{} ({}).\0",
        major_version, minor_version, build
    );
    unsafe {
        MessageBoxA(null(), message.as_ptr(), "Manifest Test\0".as_ptr(), 0x40);
    }
}

#[cfg(all(windows, not(target_env = "msvc")))]
mod manifest {
    use std::arch::global_asm;

    global_asm!(
        r#".section .rsrc$01,"dw""#,
        ".p2align 2",
        "2:",
        ".zero 14",
        ".short 1",
        ".long 24",
        ".long (3f - 2b) | 0x80000000",
        "3:",
        ".zero 14",
        ".short 1",
        ".long 1",
        ".long (4f - 2b) | 0x80000000",
        "4:",
        ".zero 14",
        ".short 1",
        ".long 1033",
        ".long 5f - 2b",
        "5:",
        ".long MANIFEST@imgrel",
        ".long 1207",
        ".zero 8",
        ".p2align 2",
    );

    #[no_mangle]
    #[link_section = ".rsrc$02"]
    static mut MANIFEST: [u8; 1207] = *br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
        </application>
    </compatibility>
    <asmv3:application>
        <asmv3:windowsSettings>
            <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
            <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
        </asmv3:windowsSettings>
    </asmv3:application>
    <asmv3:trustInfo>
        <asmv3:security>
            <asmv3:requestedPrivileges>
                <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
            </asmv3:requestedPrivileges>
        </asmv3:security>
    </asmv3:trustInfo>
</assembly>"#;
}
Enter fullscreen mode Exit fullscreen mode

The inline assembly goes in a COFF section named .rsrc$01, and the manifest string goes in a section named .rsrc$02. The linker combines these into a single .rsrc section in the executable, with .long MANIFEST@imgrel in the header being turned into a relocatable reference to the manifest string.

You can combine these two approaches to work with any of the msvc, gnu and gnullvm target environments.

The easy way

Self-promotion moment: I’ve written a crate called embed-manifest that will use the MSVC approach, or generate an object file containing the manifest data in a .rsrc section and link against it, without needing to know as much about what you’re doing:

use embed_manifest::{embed_manifest, new_manifest};

fn main() {
    if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
        embed_manifest(new_manifest("Contoso.Sample"))
            .expect("unable to embed manifest file");
    }
    println!("cargo:rerun-if-changed=build.rs");
}
Enter fullscreen mode Exit fullscreen mode

It has no external dependencies so it won’t slow your build down much at all.

Top comments (0)

DEV has this feature:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. πŸ›