Skip to main content

ferroui_winui/
windows_app_sdk.rs

1//! The Windows App SDK for a process that is not packaged: the bootstrap
2//! that adds the framework package of the runtime to the package graph of
3//! the process, so that WinUI and the other classes of the SDK can be
4//! activated.
5//!
6//! The reference has no such code: a C# application of the SDK has it
7//! generated by the build tasks of the SDK's package, which compile
8//! `MddBootstrapAutoInitializer.cs` and `UndockedRegFreeWinRT-AutoInitializer.cs`
9//! into it. A Rust application has no such tasks; it calls
10//! [`WindowsAppSdk::initialize`] before anything of WinUI, as the generated
11//! initializers do when the process starts (docs/porting/winui.md, section
12//! 3.3).
13//!
14//! `Microsoft.WindowsAppRuntime.Bootstrap.dll` is loaded at run time: the
15//! application ships it beside the executable (it is part of the package
16//! `Microsoft.WindowsAppSDK` on nuget.org, under `runtimes/win-<arch>/native`),
17//! and the runtime of the version asked for is installed on the machine
18//! (`WindowsAppRuntimeInstall-<arch>.exe`).
19
20/// What the bootstrap asks for: the version of the framework package and how
21/// a failure is reported.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct WindowsAppSdkOptions {
24    /// The major and the minor version of the release, as `0xMMMMNNNN`
25    /// (`WINDOWSAPPSDK_RELEASE_MAJORMINOR`).
26    pub major_minor_version: u32,
27    /// The tag of a preview or an experimental release; empty for a stable
28    /// one.
29    pub version_tag: String,
30    /// The lowest version of the runtime package (`PACKAGE_VERSION`); zero
31    /// for any of the release.
32    pub min_version: u64,
33    /// `MddBootstrapInitializeOptions`. The initializer the SDK generates
34    /// shows the system's dialog that offers the runtime when none is
35    /// installed (`OnNoMatch_ShowUI`); the default here is none, so that a
36    /// process without a person in front of it fails with the result code.
37    pub options: i32,
38    /// The bootstrap library: its name, which the system looks for beside
39    /// the executable first, or a path.
40    pub bootstrap_library: String,
41}
42
43impl WindowsAppSdkOptions {
44    /// `MddBootstrapInitializeOptions_None`.
45    pub const OPTIONS_NONE: i32 = 0;
46    /// `MddBootstrapInitializeOptions_OnNoMatch_ShowUI`.
47    pub const OPTIONS_ON_NO_MATCH_SHOW_UI: i32 = 0x0008;
48
49    /// The release whose metadata the bindings of the crate are generated
50    /// from (`interop/winui.idl`): 1.6, any runtime version of it.
51    pub const RELEASE_MAJOR_MINOR: u32 = 0x0001_0006;
52}
53
54impl Default for WindowsAppSdkOptions {
55    fn default() -> Self {
56        WindowsAppSdkOptions {
57            major_minor_version: Self::RELEASE_MAJOR_MINOR,
58            version_tag: String::new(),
59            min_version: 0,
60            options: Self::OPTIONS_NONE,
61            bootstrap_library: "Microsoft.WindowsAppRuntime.Bootstrap.dll".to_string(),
62        }
63    }
64}
65
66/// Parses `major.minor` (as `1.6`) into the form of
67/// [`WindowsAppSdkOptions::major_minor_version`].
68pub fn parse_major_minor(text: &str) -> Option<u32> {
69    let (major, minor) = text.trim().split_once('.')?;
70    let major: u16 = major.parse().ok()?;
71    let minor: u16 = minor.parse().ok()?;
72    Some((u32::from(major) << 16) | u32::from(minor))
73}
74
75#[cfg(windows)]
76pub use imp::WindowsAppSdk;
77
78#[cfg(windows)]
79mod imp {
80    use super::WindowsAppSdkOptions;
81    use ferroui_microcom::HResult;
82    use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
83
84    type MddBootstrapInitialize2 = unsafe extern "system" fn(u32, *const u16, u64, i32) -> i32;
85    type MddBootstrapShutdown = unsafe extern "system" fn();
86    type WindowsAppRuntimeEnsureIsLoaded = unsafe extern "system" fn() -> i32;
87
88    fn wide(text: &str) -> Vec<u16> {
89        text.encode_utf16().chain(Some(0)).collect()
90    }
91
92    /// The process is set up to use the runtime while this lives; dropping
93    /// it calls `MddBootstrapShutdown`, as the end of the process does for
94    /// the initializer the SDK generates.
95    pub struct WindowsAppSdk {
96        shutdown: MddBootstrapShutdown,
97    }
98
99    impl WindowsAppSdk {
100        /// `MddBootstrapInitialize2` of the bootstrap library, then
101        /// `WindowsAppRuntime_EnsureIsLoaded` of the runtime library, which
102        /// lets the process activate the classes of the framework package
103        /// (the two initializers the SDK generates for a C# application).
104        pub fn initialize(options: &WindowsAppSdkOptions) -> Result<WindowsAppSdk, String> {
105            let library = wide(&options.bootstrap_library);
106            // SAFETY: a terminated name. The library stays loaded for the
107            // process.
108            let module = unsafe { LoadLibraryW(library.as_ptr()) };
109            if module.is_null() {
110                return Err(format!(
111                    "{} could not be loaded (it is shipped beside the executable: the package Microsoft.WindowsAppSDK, runtimes/win-<arch>/native)",
112                    options.bootstrap_library
113                ));
114            }
115            // SAFETY: a loaded module and terminated names; the exports have
116            // the signatures of MddBootstrap.h.
117            let (initialize, shutdown) = unsafe {
118                let initialize = GetProcAddress(module, c"MddBootstrapInitialize2".as_ptr().cast())
119                    .ok_or("the bootstrap library has no MddBootstrapInitialize2")?;
120                let shutdown = GetProcAddress(module, c"MddBootstrapShutdown".as_ptr().cast())
121                    .ok_or("the bootstrap library has no MddBootstrapShutdown")?;
122                (
123                    std::mem::transmute::<unsafe extern "system" fn() -> isize, MddBootstrapInitialize2>(initialize),
124                    std::mem::transmute::<unsafe extern "system" fn() -> isize, MddBootstrapShutdown>(shutdown),
125                )
126            };
127
128            let tag = wide(&options.version_tag);
129            // SAFETY: plain values and a terminated tag that lives through
130            // the call.
131            let hr = unsafe { initialize(options.major_minor_version, tag.as_ptr(), options.min_version, options.options) };
132            if hr < 0 {
133                return Err(format!(
134                    "MddBootstrapInitialize2 failed with {} for release {}.{} (is the Windows App Runtime of that release installed?)",
135                    HResult(hr as u32),
136                    options.major_minor_version >> 16,
137                    options.major_minor_version & 0xFFFF
138                ));
139            }
140
141            // UndockedRegFreeWinRT: load the runtime library of the framework
142            // package, which makes its classes activatable for a process
143            // without a package of its own.
144            let runtime = wide("Microsoft.WindowsAppRuntime.dll");
145            // SAFETY: a terminated name; the framework package is in the
146            // package graph of the process now.
147            let runtime_module = unsafe { LoadLibraryW(runtime.as_ptr()) };
148            if runtime_module.is_null() {
149                // SAFETY: the bootstrap succeeded above.
150                unsafe { shutdown() };
151                return Err("Microsoft.WindowsAppRuntime.dll could not be loaded from the framework package".to_string());
152            }
153            // SAFETY: a loaded module and a terminated name.
154            if let Some(ensure) = unsafe { GetProcAddress(runtime_module, c"WindowsAppRuntime_EnsureIsLoaded".as_ptr().cast()) } {
155                // SAFETY: the export takes nothing and returns a result code.
156                let ensure = unsafe { std::mem::transmute::<unsafe extern "system" fn() -> isize, WindowsAppRuntimeEnsureIsLoaded>(ensure) };
157                // SAFETY: as above.
158                let hr = unsafe { ensure() };
159                if hr < 0 {
160                    // SAFETY: the bootstrap succeeded above.
161                    unsafe { shutdown() };
162                    return Err(format!("WindowsAppRuntime_EnsureIsLoaded failed with {}", HResult(hr as u32)));
163                }
164            }
165
166            Ok(WindowsAppSdk { shutdown })
167        }
168    }
169
170    impl Drop for WindowsAppSdk {
171        fn drop(&mut self) {
172            // SAFETY: the bootstrap of this value succeeded.
173            unsafe { (self.shutdown)() };
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    // Not from upstream: the reference has no such code.
181    use super::*;
182
183    #[test]
184    fn the_release_is_major_and_minor() {
185        assert_eq!(Some(0x0001_0006), parse_major_minor("1.6"));
186        assert_eq!(Some(0x0001_0008), parse_major_minor(" 1.8 "));
187        assert_eq!(None, parse_major_minor("1"));
188        assert_eq!(None, parse_major_minor("one.six"));
189        assert_eq!(WindowsAppSdkOptions::RELEASE_MAJOR_MINOR, WindowsAppSdkOptions::default().major_minor_version);
190    }
191}