use std::marker::PhantomData;
use std::path::Path;
use std::rc::Rc;
use anyhow::{Context, Result};
use windows::core::{s, w, HRESULT, HSTRING, PCSTR, PCWSTR};
use windows::Win32::Foundation::{FreeLibrary, HMODULE, HWND, S_OK};
use windows::Win32::System::LibraryLoader::{
GetProcAddress, LoadLibraryExW, LOAD_LIBRARY_FLAGS, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR,
};
use super::bindings::Microsoft::UI::Dispatching::{DispatcherQueue, DispatcherQueueController};
use super::bindings::Microsoft::UI::WindowId;
const BOOTSTRAP_DLL: &str = "Microsoft.WindowsAppRuntime.Bootstrap.dll";
const RELEASE: u32 = 0x0001_0008;
const MINIMUM_VERSION: u64 = 0x1f40_03b2_06a5_0000;
const INSTALL_HELP: &str = "install the architecture-matching Windows App Runtime 1.8 (8000.946.1701.0 or newer 1.8 servicing release) and Visual C++ Redistributable; see https://learn.microsoft.com/windows/apps/windows-app-sdk/downloads";
type Initialize = unsafe extern "system" fn(u32, PCWSTR, u64, u32) -> HRESULT;
type Shutdown = unsafe extern "system" fn();
type WindowIdFromWindow = unsafe extern "system" fn(HWND, *mut WindowId) -> HRESULT;
pub(in crate::windows) struct Runtime {
dispatcher: Dispatcher,
_interop: Module,
convert: WindowIdFromWindow,
_bootstrap: Bootstrap,
_thread: PhantomData<Rc<()>>,
}
struct Bootstrap {
_bootstrap: Module,
shutdown: Shutdown,
}
impl Runtime {
pub(in crate::windows) fn initialize() -> Result<Self> {
let executable = std::env::current_exe().context("cannot locate the desktop executable")?;
let directory = executable
.parent()
.context("desktop executable has no directory")?;
Self::from_directory(directory)
}
fn from_directory(directory: &Path) -> Result<Self> {
let bootstrap = Bootstrap::from_directory(directory)?;
let dispatcher =
Dispatcher::current().context("cannot initialize the Windows App SDK UI dispatcher")?;
let interop = Module::load(
w!("Microsoft.Internal.FrameworkUdk.dll"),
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
)
.context("cannot load Windows App SDK HWND interop from the initialized runtime")?;
let convert: WindowIdFromWindow =
unsafe { std::mem::transmute(interop.export(s!("Windowing_GetWindowIdFromWindow"))?) };
Ok(Self {
dispatcher,
_interop: interop,
convert,
_bootstrap: bootstrap,
_thread: PhantomData,
})
}
pub(super) fn window_id(&self, hwnd: HWND) -> Result<WindowId> {
let mut id = WindowId::default();
unsafe { (self.convert)(hwnd, &mut id).ok()? };
Ok(id)
}
pub(super) fn dispatcher(&self) -> &DispatcherQueue {
&self.dispatcher.queue
}
}
struct Dispatcher {
queue: DispatcherQueue,
controller: Option<DispatcherQueueController>,
}
impl Dispatcher {
fn current() -> windows::core::Result<Self> {
match DispatcherQueue::GetForCurrentThread() {
Ok(queue) => Ok(Self {
queue,
controller: None,
}),
Err(error) if error.code() == S_OK => {
let controller = DispatcherQueueController::CreateOnCurrentThread()?;
let queue = controller.DispatcherQueue()?;
Ok(Self {
queue,
controller: Some(controller),
})
}
Err(error) => Err(error),
}
}
}
impl Drop for Dispatcher {
fn drop(&mut self) {
if let Some(controller) = &self.controller {
if let Err(error) = controller.ShutdownQueue() {
eprintln!("WebUI: failed to shut down the Windows App SDK dispatcher: {error}");
}
}
}
}
impl Bootstrap {
fn from_directory(directory: &Path) -> Result<Self> {
let path = directory.join(BOOTSTRAP_DLL);
let bootstrap = Module::load(
PCWSTR(HSTRING::from(path.as_os_str()).as_ptr()),
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
)
.with_context(|| {
format!(
"cannot load {}; help: distribute the matching Windows App SDK bootstrap DLL beside the executable",
path.display()
)
})?;
let (initialize, shutdown): (Initialize, Shutdown) = unsafe {
(
std::mem::transmute::<unsafe extern "system" fn() -> isize, Initialize>(
bootstrap.export(s!("MddBootstrapInitialize2"))?,
),
std::mem::transmute::<unsafe extern "system" fn() -> isize, Shutdown>(
bootstrap.export(s!("MddBootstrapShutdown"))?,
),
)
};
initialize_runtime(initialize)?;
Ok(Self {
_bootstrap: bootstrap,
shutdown,
})
}
}
fn initialize_runtime(initialize: Initialize) -> Result<()> {
unsafe { initialize(RELEASE, w!(""), MINIMUM_VERSION, 0).ok() }
.with_context(|| format!("Windows App Runtime initialization failed; help: {INSTALL_HELP}"))
}
impl Drop for Bootstrap {
fn drop(&mut self) {
unsafe { (self.shutdown)() };
}
}
struct Module(HMODULE);
impl Module {
fn load(path: PCWSTR, flags: LOAD_LIBRARY_FLAGS) -> windows::core::Result<Self> {
unsafe { LoadLibraryExW(path, None, flags).map(Self) }
}
fn export(&self, name: PCSTR) -> windows::core::Result<unsafe extern "system" fn() -> isize> {
unsafe { GetProcAddress(self.0, name) }.ok_or_else(windows::core::Error::from_thread)
}
}
impl Drop for Module {
fn drop(&mut self) {
if let Err(error) = unsafe { FreeLibrary(self.0) } {
eprintln!("WebUI: failed to release Windows App SDK module: {error}");
}
}
}
#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
use super::*;
#[test]
fn missing_bootstrap_reports_required_companion_without_initializing_runtime() {
let directory = tempfile::tempdir().unwrap();
let error = Runtime::from_directory(directory.path()).err().unwrap();
let message = error.to_string();
assert!(message.contains(BOOTSTRAP_DLL));
assert!(message.contains("beside the executable"));
}
#[test]
fn missing_shared_runtime_reports_release_architecture_and_installer() {
unsafe extern "system" fn unavailable(_: u32, _: PCWSTR, _: u64, _: u32) -> HRESULT {
HRESULT(0x8007_3d54_u32.cast_signed())
}
let error = initialize_runtime(unavailable).err().unwrap();
let message = error.to_string();
assert!(message.contains("8000.946.1701.0"));
assert!(message.contains("architecture-matching"));
assert!(message.contains("https://learn.microsoft.com/"));
assert!(error.chain().count() > 1);
}
#[test]
fn borrowing_an_existing_ui_queue_does_not_shut_it_down() {
use super::super::bindings::Microsoft::UI::Dispatching::DispatcherQueueHandler;
let _bootstrap_lock = super::super::BOOTSTRAP_TEST_LOCK.lock().unwrap();
let _com = crate::windows::initialize_com().unwrap();
let runtime = Runtime::initialize().unwrap();
let borrowed = Dispatcher::current().unwrap();
assert!(borrowed.controller.is_none());
drop(borrowed);
let work = DispatcherQueueHandler::new(|| Ok(()));
assert!(runtime.dispatcher().TryEnqueue(&work).unwrap());
}
}