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}