Rust 프로그램에 Windows 매니페스트 포함

16418 단어 windowsrust
Windows용 독립 실행형 프로그램을 배포하는 경우 XML 애플리케이션 매니페스트를 포함해야 하지만 Cargo 및 Rust는 이를 위한 쉬운 방법을 제공하지 않습니다. 이 게시물에서는 이 작업을 직접 수행하는 방법을 보여 드리겠습니다.

Windows 버전 7~11과의 호환성을 해제하는 아래 매니페스트를 사용하여 Windows가 Vista에서 실행 중인 프로그램에 대해 거짓말을 하지 않도록 합니다. MessageBoxA와 같이 Rust 문자열을 API에 직접 전달할 수 있도록 코드 페이지를 UTF-8로 설정합니다. 레지스트리에서 경로 이름을 구성하면 긴 경로 이름을 사용할 수 있으므로 현재 디렉토리가 매우 길어집니다.

<?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>


Visual Studio 빌드 도구 방식



Windows에서 Visual Studio 빌드 도구를 사용하여 Windows용 프로그램만 컴파일하는 경우 빌드 스크립트를 사용하여 몇 가지 추가 인수를 LINK.EXE에 전달하여 Windows 매니페스트를 추가할 수 있습니다.

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");
}


이것은 what rustc does in release 1.63 입니다.

"맥주를 잡아라" 방식



MinGW 도구를 사용하여 컴파일하거나 크로스 컴파일하는 경우 인라인 어셈블리를 사용하여 매니페스트 리소스를 포함할 수 있습니다.

#![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>"#;
}


인라인 어셈블리는 .rsrc$01 라는 COFF 섹션으로 이동하고 매니페스트 문자열은 .rsrc$02 라는 섹션으로 이동합니다. 링커는 이를 실행 파일의 단일 .rsrc 섹션으로 결합하고 헤더의 .long MANIFEST@imgrel는 매니페스트 문자열에 대한 재배치 가능한 참조로 변환됩니다.

이 두 가지 접근 방식을 결합하여 msvc , gnugnullvm 대상 환경에서 작업할 수 있습니다.

쉬운 방법



자기 홍보 순간: MSVC 접근 방식을 사용하거나 .rsrc 섹션에 매니페스트 데이터가 포함된 개체 파일을 생성하고 그에 대한 링크를 생성하는 embed-manifest이라는 크레이트를 작성했습니다. 당신이 하고 있는 일:

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");
}


외부 종속성이 없으므로 빌드 속도가 전혀 느려지지 않습니다.

좋은 웹페이지 즐겨찾기