DEV Community

Cover image for Embed a Windows manifest in your Rust program
Carey Evans
Carey Evans

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 (2)

Collapse
 
carey profile image
Carey Evans

Incidentally, as of Rust 1.66 it’s no longer necessary to #[no_mangle] the MANIFEST symbol: the resource directory entry can refer to .long {}@imgrel, with sym MANIFEST added at the end of the global_asm! lines. Once asm_const has been stabilised, the length won’t need to be hard-coded either.

Collapse
 
knutaf profile image
knut

This is so rad. I've had to embed a manifest for using registry
-free COM before, and it was just a ton of fiddling to get it right. Anything that removes some sharp edges is great.