Technical Documentation: microsoft/windows-rs
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`docs/readme.md`)
windows-rs provides Rust crates for Windows APIs, from COM support and system services to UI and
raw bindings.
Prefer the focused crates below when they cover the functionality you need. For additional APIs,windows-bindgen can generate a minimal project-specific binding,
while windows and windows-sys provide broad API
coverage behind feature flags for binary applications.
Reusable libraries should avoid the windows and windows-sys umbrella crates. They add
dependency weight and version churn, and several libraries can bring incompatible versions of the
same large crate into one dependency tree. Prefer the focused crates for shared Windows types and
generate any remaining private bindings with windows-bindgen.
Each crate page covers usage and maintenance. Generated API documentation is available on
docs.rs.
Crates
Core types and interop
| Crate | Description |
|---|---|
| windows-core | COM and WinRT types, traits, and authoring macros. |
| windows-result | Windows error handling and propagation. |
| windows-strings | Windows string types, conversions, and macros. |
| windows-link | Links C-style functions without import libraries. |
Values and collections
| Crate | Description |
|---|---|
| windows-collections | WinRT collections. |
| windows-numerics | Graphics vectors and matrices. |
| windows-reference | Implementation of WinRT IReference<T>. |
| windows-time | WinRT DateTime and TimeSpan types. |
Async and threading
| Crate | Description |
|---|---|
| windows-future | WinRT asynchronous operations as Rust futures. |
| windows-threading | Safe wrapper over the Win32 thread pool. |
System services
| Crate | Description |
|---|---|
| windows-registry | Safe Windows registry access. |
| windows-services | Support for authoring Windows services. |
| windows-version | Queries the Windows version at runtime. |
UI and graphics
| Crate | Description |
|---|---|
| windows-reactor | Declarative UI library backed by WinUI 3. |
| windows-canvas | 2D graphics built on Direct2D. |
| windows-composition | Windows composition visuals. |
| windows-webview | Safe wrapper around the WebView2 browser control. |
| windows-window | Window creation and message dispatch. |
| windows-pickers | Windows file and folder pickers. |
| windows-animation | Wrapper around the Windows Animation Manager. |
| windows-reactor-setup | Stages the Windows App Runtime. |
Code generation and metadata
| Crate | Description |
|---|---|
| windows-clang | Generates RDL from C and C++ headers using libclang. |
| windows-default | Embedded Windows metadata for build tools. |
| windows-metadata | Reads and writes ECMA-335 metadata. |
| windows-rdl | Parses RDL and generates ECMA-335 metadata. |
| cppwinrt | Packages the C++/WinRT compiler. |
Macro implementation
These crates implement macros exported by windows-core. Applications should use thewindows-core exports rather than depend on them directly.
| Crate | Description |
|---|---|
| windows-implement | Implements the #[implement] macro. |
| windows-interface | Implements the #[interface] macro. |
Windows API bindings
Use windows-bindgen when the focused crates above do not cover the APIs you need. Binary
applications may instead prefer the convenience of the pre-generated windows or windows-sys
umbrella crate.
| Crate | Description |
|---|---|
| windows-bindgen | Generates Rust bindings from Windows metadata. |
| windows | Typed bindings for C-style, COM, and WinRT APIs. |
| windows-sys | Raw bindings for C-style Windows APIs. |
Overview (`metadata/win32/readme.md`)
Win32 metadata snapshot
This directory holds the canonical RDL snapshot of the Win32 API surface thatwindows-clang scrapes from the Windows SDK headers. The whole
API lives in a single flat Windows.Win32 namespace, split one file per defining header -
e.g. wingdi.rdl holds exactly the declarations wingdi.h defines - written in the human-readable
RDL text format. The partitioning follows the source (the clang
cursor's defining header), not a hand-curated namespace layout.
The merged binary Windows.Win32.winmd that downstream tooling consumes is not checked in (winmd
is a binary format, and *.winmd is git-ignored). This text snapshot is committed instead so the
API surface can be browsed directly and so changes show up as readable diffs in pull requests - the
same reason the generated windows and windows-sys crates are committed.
Generated - do not edit by hand
These files are produced by tool-win32:
cargo run -p tool-win32Re-run the tool after changing the manifest (the const slices incrates/tools/win32/src/main.rs), the hand-authored vocabulary seed (metadata/metadata.rdl), or
the scraper, and commit the resulting diff. Hand edits will be overwritten on the next run - the
tool clears the generated partitions in this directory and re-emits the whole closure. The seed
lives one level up (metadata/metadata.rdl) so it survives that clear.
See docs/crates/windows-clang.md for the scraper design -
how the metadata matches the SDK headers, header partitioning, and the documented deviations from
the reference - and docs/crates/windows-rdl.md for the winmd
artifact and layout of the in-house metadata pipeline that replaces the external win32metadata
project.
Overview (`crates/libs/animation/readme.md`)
windows-animation
Windows Animation wraps the Windows Animation Manager (IUIAnimationManager) for variable
interpolation, transitions, and storyboards. It does not depend on a UI or rendering framework.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-animation]
version = "0.100"use windows_animation::*;
fn main() -> Result<()> {
let manager = Manager::new()?;
let opacity = manager.create_variable(0.0)?;
// Animate from 0.0 to 1.0 over half a second.
let library = TransitionLibrary::new()?;
let fade_in = library.linear(0.5, 1.0)?;
let storyboard = manager.create_storyboard()?;
storyboard.add_transition(&opacity, &fade_in)?;
storyboard.schedule(0.0)?;
// Each frame, advance the clock and read the current value.
manager.update(0.25)?;
let value = opacity.value()?;
assert!((0.0..=1.0).contains(&value));
Ok(())
}Overview (`crates/libs/bindgen/readme.md`)
windows-bindgen
The windows-bindgen crate generates Rust bindings from
Windows metadata.
Add the generator as a build dependency and the generated code's runtime dependency:
[dependencies.windows-link]
version = "0.100"
[build-dependencies.windows-bindgen]
version = "0.100"Generate a flat file in OUT_DIR from build.rs:
use std::path::Path;
fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
windows_bindgen::builder()
.output(Path::new(&out_dir).join("bindings.rs"))
.filter("GetTickCount")
.flat()
.sys()
.write();
}Filters name the functions, constants, or types you need. Signature dependencies are included
automatically. See the guide for qualified names, member filters, and filter files.
Use the generated bindings from src/main.rs:
mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
fn main() {
unsafe {
println!("{}", bindings::GetTickCount());
}
}For a published library, generate and commit src/bindings.rs from a separate tool instead of
running the generator in consumer builds. Formatting is best effort; install rustfmt when
generating checked-in source.
Overview (`crates/libs/canvas/readme.md`)
windows-canvas
Windows Canvas wraps Direct2D, Direct3D 11, DXGI, DirectWrite, and WIC for 2D drawing.
The core crate is host-neutral. Enable system for the typed windows-window swap-chain helper,reactor for Canvas controls in Reactor, and composition to draw into Composition surfaces. Thesystem and reactor host adapters can coexist unless composition selects their incompatible
Composition backends.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-canvas]
version = "0.100"use windows_canvas::*;
fn main() -> Result<()> {
let device = GpuDevice::new_warp()?;
let mut swap_chain = device.create_swap_chain(640, 480)?;
let session = swap_chain.begin_draw()?;
session.clear(ColorF::CORNFLOWER_BLUE);
let brush = session.create_solid_brush(ColorF::rgb(1.0, 0.5, 0.0))?;
session.fill_rect(&Rect::new(20.0, 20.0, 200.0, 120.0), &brush);
drop(session);
swap_chain.present()?;
Ok(())
}Overview (`crates/libs/clang/readme.md`)
windows-clang
The windows-clang crate extracts declarations from C and
C++ source with libclang and emits RDL. It is the
header-facing stage of the Windows metadata pipeline:
headers -> windows-clang -> RDL -> windows-rdl -> WinMDAdd the crate to your Cargo.toml:
[dependencies.windows-clang]
version = "0.100"For ordinary header-to-RDL generation, configure and run the high-level builder:
windows_clang::clang()
.input("Example.h")
.args(["-x", "c++", "--target=x86_64-pc-windows-msvc"])
.reference_default()
.namespace("Example")
.library("example.dll")
.output("Example.rdl")
.write()
.unwrap();The builder reads inputs and references, invokes the extractor, emits RDL, and writes the output.
Use Input, extract, and EmitOptions directly when a generator needs to inspect or
combine immutable snapshots before emission.
The caller owns libclang installation, compiler arguments, package versions, import-library
discovery, architecture merging, output promotion, and RDL-to-WinMD compilation. See the crate
documentation for both APIs and the extraction model.
Overview (`crates/libs/collections/readme.md`)
windows-collections
The windows-collections crate implements Windows
collection interfaces for Rust collections.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-collections]
version = "0.100"use windows_collections::*;
let numbers = IIterable::<i32>::from(vec![1, 2, 3]);
for value in numbers {
println!("{value}");
}Overview (`crates/libs/composition/readme.md`)
windows-composition
Windows Composition wraps the retained-mode Windows composition engine for standalone windows and
WinUI 3 applications.
The default system feature hosts Composition in a windows-window window. Use the reactor
feature for a Composition host inside Reactor, with default features disabled. The stacks are
mutually exclusive. Both support Canvas drawing surfaces through windows-canvas[composition];
select Canvas's matching system or reactor feature.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-composition]
version = "0.100"use windows_composition::*;
fn build(compositor: &Compositor) -> SpriteVisual {
let visual = compositor.create_sprite_visual();
visual.set_size(200.0, 120.0);
let brush = compositor.create_color_brush(CompositionColor::rgb(0, 120, 215));
visual.set_brush(&brush);
visual
}Overview (`crates/libs/core/readme.md`)
windows-core
The windows-core crate provides COM and WinRT types,
traits, and macros for the windows-* family of crates.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-core]
version = "0.100"Use the core types as needed:
use windows_core::{h, Result, HRESULT, HSTRING};
// WinRT reference-counted strings.
let name: &HSTRING = h!("Windows.Foundation.Uri");
assert_eq!(name, &HSTRING::from("Windows.Foundation.Uri"));
// HRESULT-based results.
fn check(code: HRESULT) -> Result<()> {
code.ok()
}
assert!(check(HRESULT(0)).is_ok());
assert!(check(HRESULT(-2147467259)).is_err());create_instance activates an in-process COM class and returns a requested interface. The
application must initialize the calling thread for COM before using it.
Use init_mta for apartment-agnostic code. UI threads that require a single-threaded apartment can
hold the value returned by init_sta for the lifetime of their COM objects.
Overview (`crates/libs/cppwinrt/readme.md`)
cppwinrt
The cppwinrt crate bundles the C++/WinRT compiler for use in
Rust.
Start by adding the following to your Cargo.toml file:
[dependencies.cppwinrt]
version = "0.100"Use cppwinrt function as needed:
println!("{}", cppwinrt::cppwinrt(["-help"]));Overview (`crates/libs/default/readme.md`)
windows-default
The windows-default crate provides the default metadata
for Windows APIs as embedded byte slices. Build tools can use WINRT and WIN32 without
locating or distributing separate .winmd files.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-default]
version = "0.100"use windows_default::{WIN32, WINRT};
assert!(!WINRT.is_empty());
assert!(!WIN32.is_empty());Overview (`crates/libs/future/readme.md`)
windows-future
The windows-future crate adapts WinRT asynchronous
operations to Rust futures.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-future]
version = "0.100"Use the Windows async types as needed:
use windows_future::*;
// This result will be available immediately.
let ready = IAsyncOperation::ready(Ok(123));
assert_eq!(ready.join().unwrap(), 123);
let ready = IAsyncOperation::spawn(|| {
// Some lengthy operation goes here...
Ok(456)
});
assert_eq!(ready.join().unwrap(), 456);Overview (`crates/libs/implement/readme.md`)
windows-implement
The windows-implement crate provides the#[implement] macro for implementing COM and WinRT interfaces in Rust.
Start by adding the following to your Cargo.toml file:
[dependencies]
windows-core = "0.100"
windows-implement = "0.100"use windows_core::{IUnknown, interface};
use windows_implement::implement;
#[interface("57f61c8b-b400-4035-a7c2-abbddf9e3559")]
unsafe trait IValue: IUnknown {
fn get(&self) -> u32;
}
#[implement(IValue)]
struct Value(u32);
impl IValue_Impl for Value_Impl {
unsafe fn get(&self) -> u32 {
self.0
}
}
let value: IValue = Value(42).into();
assert_eq!(unsafe { value.get() }, 42);Overview (`crates/libs/interface/readme.md`)
windows-interface
The windows-interface crate provides the#[interface] macro for declaring COM interfaces in Rust.
Start by adding the following to your Cargo.toml file:
[dependencies]
windows-core = "0.100"
windows-interface = "0.100"use windows_core::IUnknown;
use windows_interface::interface;
#[interface("57f61c8b-b400-4035-a7c2-abbddf9e3559")]
unsafe trait IValue: IUnknown {
fn get(&self) -> u32;
}Overview (`crates/libs/link/readme.md`)
windows-link
The windows-link crate provides the link macro that
simplifies linking. The link macro uses raw-dylib and thus does not require import lib files.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-link]
version = "0.100"Use the link macro to import functions and their matching function-pointer types. Here the loader
functions are linked normally while MessageBoxA is resolved at runtime:
use std::ffi::c_void;
use windows_link::link;
link!("kernel32.dll" "system" fn LoadLibraryA(name: *const u8) -> *mut c_void);
link!("kernel32.dll" "system" fn GetProcAddress(
library: *mut c_void,
name: *const u8,
) -> *mut c_void);
link!("user32.dll" "system" fn MessageBoxA(
hwnd: *mut c_void,
text: *const u8,
caption: *const u8,
kind: u32,
) -> i32);
unsafe {
let module = LoadLibraryA(b"user32.dll\0".as_ptr());
let address = GetProcAddress(module, b"MessageBoxA\0".as_ptr());
if !address.is_null() {
let message_box: MessageBoxA = std::mem::transmute(address);
message_box(
std::ptr::null_mut(),
b"text\0".as_ptr(),
b"caption\0".as_ptr(),
0,
);
}
}Overview (`crates/libs/metadata/readme.md`)
windows-metadata
The windows-metadata crate reads and writes the
ECMA-335 metadata format used by .NET, WinRT, and Win32 metadata.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-metadata]
version = "0.100"Query a type with the metadata reader:
use windows_metadata::*;
let index = reader::Index::read("Windows.winmd").unwrap();
let def = index.expect("Windows.Foundation", "Point");
assert_eq!(def.namespace(), "Windows.Foundation");
assert_eq!(def.name(), "Point");
let extends = def.extends().unwrap();
assert_eq!(extends.namespace(), "System");
assert_eq!(extends.name(), "ValueType");
let fields: Vec<_> = def.fields().collect();
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].name(), "X");
assert_eq!(fields[1].name(), "Y");
assert_eq!(fields[0].ty(), Type::F32);
assert_eq!(fields[1].ty(), Type::F32);Overview (`crates/libs/numerics/readme.md`)
windows-numerics
The windows-numerics crate provides Windows vector,
matrix, plane, and quaternion types.
These are the same graphics math types used by composition, Direct2D, and Direct3D
APIs, so values flow into those APIs without conversion.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-numerics]
version = "0.100"Use the vector and matrix types as needed:
use windows_numerics::*;
let a = Vector2::new(3.0, 4.0);
assert_eq!(a.length(), 5.0);
let b = Vector3::new(1.0, 0.0, 0.0);
let c = Vector3::new(0.0, 1.0, 0.0);
assert_eq!(b.cross(&c), Vector3::new(0.0, 0.0, 1.0));
let transform = Matrix3x2::translation(10.0, 20.0);
assert_eq!(transform.m31, 10.0);
assert_eq!(transform.m32, 20.0);Overview (`crates/libs/pickers/readme.md`)
windows-pickers
Windows Pickers provides file and folder pickers backed by the Windows Common Item Dialog. It
supports standalone Windows applications and components built with windows-reactor.
Start by adding the following to your Cargo.toml file:
[dependencies]
windows-pickers = "0.100"
windows-window = "0.100"# #[cfg(feature = "system")]
# mod example {
use windows_pickers::{OpenFilePicker, Result};
use windows_window::Window;
fn pick_rust_file(window: &Window) -> Result<()> {
let path = OpenFilePicker::new()
.title("Open a Rust source file")
.filter_extensions("Rust source", ["rs"])
.filter_all()
.show(window)?;
if let Some(path) = path {
println!("{}", path.display());
}
Ok(())
}
# }The calling thread must be a COM single-threaded apartment (STA). Cancellation returns Ok(None);
other Windows failures are returned as errors.
Overview (`crates/libs/rdl/readme.md`)
windows-rdl
The windows-rdl crate compiles RDL (Rust Definition
Language) - a Rust-like text format for describing Windows APIs - into ECMA-335 .winmd metadata,
and back again.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-rdl]
version = "0.100"Use the reader to compile .rdl source into a .winmd, and the writer to regenerate canonical.rdl from a .winmd:
windows_rdl::reader()
.input("example.rdl")
.output("example.winmd")
.write()
.unwrap();
windows_rdl::writer()
.input("example.winmd")
.output("example.rdl")
.write()
.unwrap();Overview (`crates/libs/reactor-setup/readme.md`)
windows-reactor-setup
Windows Reactor Setup stages the Windows App SDK runtime files needed by awindows-reactor application that runs
fully self-contained.
Add it as a build dependency:
[build-dependencies]
windows-reactor-setup = "0.100"Call the setup function from build.rs:
windows_reactor_setup::as_self_contained();Runtime extraction caches are separate for each target architecture and are reused only after
successful extraction and required-file checks. Download, extraction, and copy errors fail the
build rather than leaving it to succeed with missing runtime files.
Overview (`crates/libs/reactor/readme.md`)
windows-reactor
Windows Reactor is a typed declarative UI library for building native WinUI 3 applications in
Rust. Components own Rust state, describe the current view, and receive typed messages from
controls.
Add Reactor to Cargo.toml:
[dependencies.windows-reactor]
version = "0.100"use windows_reactor::*;
#[derive(Clone, Copy)]
enum Message {
Increment,
Reset,
}
struct Counter {
count: i32,
}
impl Component for Counter {
type Input = ();
type Message = Message;
fn create(_input: &(), _context: &ComponentContext<Self>) -> Self {
Self { count: 0 }
}
fn update(&mut self, message: Message, _context: &ComponentContext<Self>) {
match message {
Message::Increment => self.count += 1,
Message::Reset => self.count = 0,
}
}
fn view(&self, _input: &(), context: &mut ViewContext<Self>) -> View {
let content = StackPanel::new().spacing(8.0).children((
format!("Count: {}", self.count),
Button::new()
.on_click(context.message(Message::Increment))
.content("Increment"),
Button::new()
.on_click(context.message(Message::Reset))
.content("Reset"),
));
context.window_frame("Counter", content)
}
}
fn main() {
App::run_component::<Counter>(()).unwrap();
}Run with cargo run. Applications use the default features and an installed Windows App SDK
runtime. See the setup guide for runtime prerequisites and self-contained deployment.
The getting-started guide covers editable state, child components, background work, images,
and application windows. Browse the samples for complete applications and focused examples.
Overview (`crates/libs/reference/readme.md`)
windows-reference
The windows-reference crate implementsIReference<T> for boxed Windows values.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-reference]
version = "0.100"use windows_reference::*;
let value = IReference::<i32>::from(42);
assert_eq!(value.Value().unwrap(), 42);Overview (`crates/libs/registry/readme.md`)
windows-registry
The windows-registry crate reads and writes the Windows
registry.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-registry]
version = "0.100"use windows_registry::*;
fn main() -> Result<()> {
let key = CURRENT_USER.create(r"software\windows-rs")?;
key.set_u32("number", 123)?;
key.set_string("name", "Rust")?;
println!("{}", key.get_u32("number")?);
println!("{}", key.get_string("name")?);
Ok(())
}Overview (`crates/libs/result/readme.md`)
windows-result
The windows-result crate provides Windows error types for
Win32, COM, and WinRT APIs.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-result]
version = "0.100"Use the HRESULT, Error, and specialized Result types as needed:
use windows_result::*;
const S_OK: HRESULT = HRESULT(0);
const ERROR_CANCELLED: WIN32_ERROR = WIN32_ERROR(1223);
const E_CANCELLED: HRESULT = ERROR_CANCELLED.to_hresult();
fn main() -> Result<()> {
S_OK.ok()?;
let e = Error::new(E_CANCELLED, "test message");
assert_eq!(e.code(), E_CANCELLED);
assert_eq!(e.message(), "test message");
Ok(())
}Overview (`crates/libs/services/readme.md`)
windows-services
The windows-services crate implements Windows service
processes.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-services]
version = "0.100"windows_services::Service::new()
.can_pause()
.can_stop()
.run(|_service, _command| {})
.unwrap();Overview (`crates/libs/strings/readme.md`)
windows-strings
The windows-strings crate provides Windows string types
and literals.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-strings]
version = "0.100"Use the Windows string types as needed:
use windows_strings::*;
const A: PCSTR = s!("ansi");
const W: PCWSTR = w!("wide");
fn main() {
let b = BSTR::from("bstr");
let h = HSTRING::from("hstring");
assert_eq!(b, "bstr");
assert_eq!(h, "hstring");
assert_eq!(unsafe { A.to_string().unwrap() }, "ansi");
assert_eq!(unsafe { W.to_string().unwrap() }, "wide");
}Overview (`crates/libs/sys/readme.md`)
windows-sys
The windows-sys crate provides raw Windows API declarations, structs, and constants without
wrappers or helper traits.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-sys]
version = "0.100"
features = [
"handleapi",
"synchapi",
"winuser",
]Make use of any Windows APIs as needed:
use windows_sys::{core::*, Win32::*};
unsafe {
let event = CreateEventW(std::ptr::null_mut(), 1, 0, std::ptr::null());
SetEvent(event);
WaitForSingleObject(event, 0);
CloseHandle(event);
MessageBoxA(0 as _, s!("Ansi"), s!("Caption"), MB_OK as u32);
MessageBoxW(0 as _, w!("Wide"), w!("Caption"), MB_OK as u32);
}Overview (`crates/libs/threading/readme.md`)
windows-threading
The windows-threading crate wraps the Windows thread
pool.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-threading]
version = "0.100"Submit work to the default pool:
windows_threading::submit(|| {
println!("thread: {}", windows_threading::thread_id());
loop {
println!(".");
windows_threading::sleep(1000);
}
});Process an iterator in parallel:
let counter = std::sync::RwLock::<usize>::new(0);
windows_threading::for_each(0..10, |value| {
println!("thread: {}, value: {value}", windows_threading::thread_id());
let mut counter = counter.write().unwrap();
*counter += value;
});
assert_eq!(*counter.read().unwrap(), 45);Overview (`crates/libs/time/readme.md`)
windows-time
The windows-time crate provides Windows TimeSpan andDateTime values with Rust constructors, arithmetic, and std::time conversions.
TimeSpan is a duration and DateTime is a point in time. Both use 100-nanosecond ticks.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-time]
version = "0.100"Use the time types as needed:
use windows_time::*;
let span = TimeSpan::from_minutes(90);
assert_eq!(span.whole_seconds(), 5400);
let start = DateTime::from_unix_secs(1_000_000_000);
let later = start.checked_add(span).unwrap();
assert_eq!(later.unix_secs(), 1_000_005_400);Overview (`crates/libs/version/readme.md`)
windows-version
The windows-version crate reads the Windows version
without relying on the application manifest.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-version]
version = "0.100"use windows_version::*;
println!("Current version: {:?}", OsVersion::current());
if is_server() {
println!("Running on a Windows Server release.");
}
if OsVersion::current() >= OsVersion::new(10, 0, 0, 12345) {
println!("Can use a feature available on this version or later.")
}Overview (`crates/libs/webview/readme.md`)
windows-webview
Windows WebView wraps the WebView2 COM APIs to host Microsoft Edge
(Chromium) in a window.
The crate has no default UI framework. Enable system for a complete windows-window host orreactor for a WebView2 control in a Reactor view. Enable both when an application uses both host
types.
Start by adding the following to your Cargo.toml file:
[dependencies]
windows-webview = { version = "0.100", features = ["system"] }WebViewWindowBuilder::run owns the COM apartment, parent window, WebView2 environment and
controller, resize handling, shutdown order, and the application's windows-window message loop:
use windows_webview::*;
fn main() -> Result<()> {
WebViewWindow::new("WebView2")
.size(1000, 700)
.run(|host| {
host.webview()
.navigate("https://github.com/microsoft/windows-rs")?;
Ok(())
})
}Use WebViewHost::builder when another framework owns the parent HWND and message loop. It chains
environment and controller creation into one callback and returns one host object for the
framework to retain. The raw-window sample shows the raw HWND
contract. The reactor sample embeds WebView2 in a Reactor view,
while reactor-window uses Reactor controls to drive a
WebView hosted in a separate windows-window.
Overview (`crates/libs/window/readme.md`)
windows-window
The windows-window crate provides basic window creation
and message-loop support for Canvas, WebView2, and custom rendering.
Start by adding the following to your Cargo.toml file:
[dependencies.windows-window]
version = "0.100"use windows_window::*;
fn main() -> Result<()> {
let window = Window::new("Hello")
.client_size(800, 600)
.on_resize(|width, height| {
println!("resized to {width} x {height}");
})
.on_close(|| {
println!("release HWND-hosted resources here");
})
.quit_on_close(true)
.create()?;
// `window.hwnd()` can be handed to windows-canvas, WebView2, Direct2D, etc.
println!("created window {:?}", window.hwnd());
run();
Ok(())
}Overview (`crates/libs/windows/readme.md`)
windows
The windows crate provides Rust bindings for Win32, COM, and
WinRT APIs generated from
Windows metadata.
Start by adding the following to your Cargo.toml file:
[dependencies.windows]
version = "0.100"
features = [
"Data_Xml_Dom",
"handleapi",
"synchapi",
"winuser",
]Make use of any Windows APIs as needed:
use windows::{core::*, Data::Xml::Dom::*, Win32::*};
fn main() -> Result<()> {
let doc = XmlDocument::new()?;
doc.LoadXml(h!("<html>hello world</html>"))?;
let root = doc.DocumentElement()?;
assert!(root.NodeName()? == "html");
assert!(root.InnerText()? == "hello world");
unsafe {
let event = CreateEventW(None, true, false, None);
if event.is_null() {
return Err(Error::from_thread());
}
SetEvent(event).ok()?;
WaitForSingleObject(event, 0);
CloseHandle(event).ok()?;
MessageBoxA(None, s!("Ansi"), s!("Caption"), MB_OK as u32);
MessageBoxW(None, w!("Wide"), w!("Caption"), MB_OK as u32);
}
Ok(())
}Overview (`crates/tests/bench/readme.md`)
test_bench
A side-by-side WinRT projection benchmark. Three consumers call the identical Rust WinRT
component, so every per-call delta is projection cost.
| Consumer | Column | Projection |
|---|---|---|
rust |
windows-rs | The generated windows-rs projection. |
cpp |
cppwinrt | The header-only C++/WinRT projection. |
cswinrt2 |
CsWinRT 2 | The stable C#/WinRT projection with RCW-backed managed classes. |
The cswinrt project is a focused CsWinRT 3.0 preview probe rather than a benchmark column. The
preview package builds on plain net10.0, but it does not contain the WinRT.Interop assembly
that its delegate marshaller loads. The probe fails at the first event subscription and its Cargo
test remains ignored until a complete preview ships.
The component (component -> bench_component.dll) is a real WinRT component: RDL -> winmd ->
windows-bindgen -> #[implement], activated registration-free via DllGetActivationFactory. It
exposes one activatable class, Bench.Widget, with scalar, string, metadata Object, nullable
reference, async, event, vector, map, and view operations. A non-default INonDefault interface
provides the QueryInterface fixture, and LiveCount reports native ownership balance.
Running
# Full run (10,000,000 iterations, median of three runs by default)
crates/tests/bench/run.ps1
# Quick run
crates/tests/bench/run.ps1 -Iterations 100000 -Runs 1run.ps1 builds the component first so its metadata writer does not race a consumer build, builds
the three consumers, stages bench_component.dll as Bench.dll, and prints median throughput,
memory, and leak values. The lower native result and the CsWinRT result are bold.
Each consumer also has a cargo test that runs the same path with a small iteration count, so CI
exercises the matrix wherever the .NET SDK and MSVC toolchain are present.
Results
Measured on August 5, 2026 with .NET SDK 10.0.302 and CsWinRT 2.3.1. Times are milliseconds for
10,000,000 operations (median of three runs); Error uses 1,000,000 operations. Lower is better.
| Metric | C++/WinRT | windows-rs | CsWinRT 2 |
|---|---|---|---|
| Create | 624 | 567 | 11,075 |
| Int32 | 25 | 24 | 45 |
| String | 298 | 291 | 1,588 |
| Add | 14 | 17 | 25 |
| Interface | 14 | 17 | 64 |
| Object | 141 | 138 | 1,205 |
| Event | 219 | 218 | 929 |
| AddRemove | 353 | 781 | 24,526 |
| Vector | 119 | 121 | 254 |
| IterateVector | 1,287 | 129 | 4,516 |
| GetMany | 2 | 2 | 183 |
| Map | 722 | 579 | 17,012 |
| Lookup | 219 | 183 | 297 |
| VectorView | 19 | 24 | 139 |
| MapView | 152 | 167 | 246 |
| Reference | 2,323 | 766 | 24,049 |
| Async | 579 | 490 | 54,547 |
| Error | 21,263 | 7 | 2,858 |
| Memory (bytes/object) | 8 | 8 | 296 |
| Leak (live objects) | 0 | 0 | 0 |
Metrics
| Metric | What it measures |
|---|---|
| Create | Activate a fresh Widget. |
| Int32 | Set and get the Int32 property. |
| String | Set and get the String property, including HSTRING conversion. |
| Add | Call a method with two Int32 arguments and a return value. |
| Interface | Acquire INonDefault once, then call Value repeatedly. |
| Object | Set and get metadata Object (IInspectable). |
| Event | Subscribe one handler and raise Changed through Signal. |
| AddRemove | Subscribe and unsubscribe a Changed handler each iteration. |
| Vector | Read one element per iteration from an IVector. |
| IterateVector | Iterate the whole IVector for a bounded number of passes. |
| GetMany | Copy a vector into caller-owned storage through the public bulk-copy surface. |
| Map | Enumerate values from an IMap<string, int>. |
| Lookup | Read one value per iteration from an IMap. |
| VectorView | Read one element per iteration from an IVectorView. |
| MapView | Read one value per iteration from an IMapView. |
| Reference | Box and unbox a nullable Int32 through IReference. |
| Async | Consume an already-completed IAsyncOperation. |
| Error | Propagate a failing HRESULT with a reduced iteration count. |
| Memory | Client-side allocated bytes per retained live object. |
| Leak | Component live-instance count above baseline after the run. |
The timed bodies use the same loop bounds, inputs, result retention, collection sizes, and reduced
error count wherever the public projection surfaces permit. CsWinRT finalization is forced outside
affected timers so cleanup does not contaminate the following metric.
The GetMany row requests the same final result through each public projection. windows-rs and
C++/WinRT expose the WinRT GetMany method directly. CsWinRT maps IVector<T> to IList<T>, so
its public CopyTo adapter loops over GetAt.
The Error row highlights projection error models. Rust observes a failed HRESULT as a Result
branch. C++/WinRT and CsWinRT throw and catch exceptions, so this row uses a reduced loop count.
The leak row checks native AddRef/Release balance. Rust and C++/WinRT release deterministically
at scope exit. CsWinRT returns to baseline after GC.Collect() andWaitForPendingFinalizers() because an RCW retains its native reference until finalization.
To add a metric, emit a matching Name: <ms> ms line from every consumer and add its name to the$Metrics array in run.ps1.
Overview (`crates/samples/reactor/content-dialog/readme.md`)
ContentDialog default button
Run cargo run -p reactor-content-dialog.
Choose a default button, then press Enter in the dialog. WinUI highlights the default button and
normally focuses it when the dialog opens. The result is shown after the dialog closes; Cancel
returns ContentDialogResult::None.
The editor case shows the focus exception: a multiline TextBox receives focus and consumes Enter
to insert a line instead of closing the dialog. Tab moves focus to the buttons.
ContentDialogButton::None removes the default-button designation, not the buttons' own keyboard
behavior. A focused button can still respond to Enter.
Overview (`crates/samples/reactor/font-icon/readme.md`)
FontIcon size and foreground
Run cargo run -p reactor-font-icon.
FontIcon::new()
.glyph("\u{E80F}")
.font_size(48.0)
.foreground(ThemeBrush::PrimaryText)Compare small red and large blue glyphs, a theme-aware foreground, and the native defaults on the
same icon. "Toggle theme" switches the window between light and dark.
Without a foreground override, the preview inherits its color from the surrounding HyperlinkButton.
"Native defaults" removes both overrides, restoring the native font size and inherited foreground.font_size accepts finite positive values. foreground accepts a Color or a ThemeBrush.
Overview (`crates/samples/reactor/gallery/readme.md`)
Reactor gallery
This sample is the windows-reactor port of the WinUI control gallery. It contains 65
destinations across 11 categories:
- Basic Input
- Collections
- Date and Time
- Design Guidance
- Dialogs and Flyouts
- Layout
- Media
- Menus and Toolbars
- Navigation
- Status and Info
- Text
Run it with:
cargo run -p reactor-galleryThe shell uses a generated TitleBar and NavigationView. The pane lists categories by default.
Select a category to open its card grid, then select a card to open the control page. Search
replaces the category links with matching control destinations. Back navigation follows the
shell's page history.
Each control destination is a component under src/pages/. Pages use controlled state for live
examples such as text input, sliders, selection controls, dialogs, progress indicators, and layout
changes. Page-owned state is recreated when a destination is retired and later reopened. Window
state, including the selected theme and backdrop material, belongs to the shell and persists across
page replacement.
For implementation examples, read the compiled page components in src/pages/.
The gallery does not duplicate them as display-only source snippets, which can drift from the
working examples.
The gallery is organized as follows:
| File | Purpose |
|---|---|
src/shell.rs |
Window visuals, title bar, navigation pane, search, and history |
src/registry.rs |
Category and destination metadata |
src/router.rs |
Leaf destination to component mapping |
src/controls.rs |
Shared page headers, sample cards, card grids, and assets |
src/pages/ |
Category modules and control page components |
Bundled images in assets/ are loaded through Image::source_file. Sample cards use WinUI theme
brushes so their surfaces follow the selected light, dark, or system theme. The Materials page
changes the live window backdrop between Mica, Mica Alt, Acrylic, and a solid background.
The gallery uses generated GridView selection feedback, BreadcrumbBar item-click events, and
the Viewbox child slot. Flyout, SplitButton, TabView, and InfoBar examples use their native event
and ownership contracts rather than substituting pointer handlers.
Recording-runtime tests mount the full gallery, replace pages through shell navigation, mount every
registered destination, drive representative controlled input, reject events from retired pages,
and verify that shell-owned theme and backdrop state survives page replacement.
Overview (`crates/samples/reactor/navigation/readme.md`)
Navigation and multi-window workspace
This sample qualifies navigation and window isolation by opening a secondary workspace from the
primary at runtime. A SplitView hosts the navigation pane and current page through generated
typed slots. Each window owns its page, editor text, focus reference, background work, and
component queue. The windows share a small application model that broadcasts theme changes and
window-lifecycle notifications.
Run it with:
cargo run -p reactor-navigationTry this sequence:
- Choose Open secondary window, then open the editor in each window and enter different text.
- Move between Home and Editor and confirm each window retains its own text.
The window title follows the current page. - Change the shared theme and confirm both windows update.
- Start background work in the secondary window, then choose Close this window.
- Confirm the primary window remains responsive and reports the secondary closure.
ComponentContext::open_window stages an independent root with the current component publication.
After publication, the host registers a pending open and mounts that root in a new Pump. The
opener's scope does not own the new Pump. Close requests use the same transactional boundary and
are applied only after publication. Each workspace declares its native title from component state.
Declarative size configuration remains deferred rather than being hidden in the sample with direct
WinUI calls.
Overview (`crates/samples/reactor/number-box/readme.md`)
NumberBox options
Run cargo run -p reactor-number-box.
NumberBox::new()
.small_change(0.25)
.spin_button_placement_mode(NumberBoxSpinButtonPlacementMode::Inline)Compare hidden, inline, and compact spinner buttons. The compact popup appears when the control
receives focus. Up/Down and the spinner buttons use small_change; values remain between 0 and 20.small_change accepts finite f64 values.
The step button switches between 0.25 and 2. "Use native defaults" removes both properties from the
existing controls, restoring a step of 1 and hidden buttons. The headers identify the custom modes;
all three use hidden buttons while the overrides are removed.
Overview (`crates/samples/reactor/startup_perf/readme.md`)
Windows Reactor startup tracing sample
This blank app demonstrates TraceLogging events across the Windows Reactor startup lifecycle. It
renders one static TextBlock in a 1000x1000 window.
The app uses the ETW team's tracelogging crate to emit TraceLogging events from theBenchmarkSyntheticApps provider:
- Provider GUID:
FD80D616-E92B-4B2B-9BED-131ADA36A8FD - Keyword:
0x0000400000000000 - App name:
blank_windows_reactor - Process name:
blank-windows-reactor.exe
| Event | Location |
|---|---|
wWinMainEntry |
Before Windows App SDK bootstrap |
XamlAppLoaded |
Root component creation |
WindowLoaded |
First post-commit effect |
FirstRender |
First CompositionTarget::Rendering callback after commit |
FirstIdle |
Low-priority dispatcher callback after FirstRender |
ProcessStop |
Immediately after the final window closes |
Every event includes AppName, Seq, and Pid fields.
Run the framework-dependent app with:
cargo run -p reactor-startup-perf --releaseMSIX package
Run these commands from the repository root:
Install Windows App Development CLI 0.5.0 or later, or upgrade an existing installation:
powershell# First installation: winget install Microsoft.WinAppCli --source winget # Existing installation: winget upgrade Microsoft.WinAppCli --source winget winapp --versionBuild the app and prepare the package layout:
powershellcargo build -p reactor-startup-perf --release --quiet $work = "target\reactor-startup-msix" $layout = "$work\layout" Remove-Item $layout -Recurse -Force -ErrorAction Ignore New-Item $layout -ItemType Directory | Out-Null Copy-Item ` target\release\blank-windows-reactor.exe ` $layout $output = "$work\blank-windows-reactor_x64.msix" $manifest = "crates\samples\reactor\startup_perf\package\Package.appxmanifest"Create an unsigned package:
powershellwinapp pack $layout --manifest $manifest --output $output --skip-priTo sign without a timestamp, use one
winapp packcall:powershell$certificate = "C:\certificates\package.pfx" $securePassword = Read-Host "Certificate password" -AsSecureString $certificatePassword = [Net.NetworkCredential]::new("", $securePassword).Password winapp pack $layout ` --manifest $manifest ` --output $output ` --skip-pri ` --cert $certificate ` --cert-password $certificatePasswordwinapp packcannot timestamp a signature. For timestamped signing, package first and then
sign:powershell$certificate = "C:\certificates\package.pfx" $timestampUrl = "https://timestamp.example.com" $securePassword = Read-Host "Certificate password" -AsSecureString $certificatePassword = [Net.NetworkCredential]::new("", $securePassword).Password winapp pack $layout --manifest $manifest --output $output --skip-pri winapp sign $output ` $certificate ` --password $certificatePassword ` --timestamp $timestampUrl
The checked-in manifest uses version 1.0.0.0 and the Microsoft corporate publisher. Update itsIdentity before packaging if either value needs to change. The publisher must exactly match the
signing certificate subject.
WinApp CLI accepts local and UNC certificate paths, but it passes the path directly to signtool;
copy the PFX locally first if the signing environment does not allow signtool to read the share.
Certificate passwords are command-line arguments because WinApp CLI does not provide a secure input
or environment-variable option.
The package remains framework-dependent. Install Microsoft.WindowsAppRuntime.2.msix on the target
machine before launching it.
Overview (`crates/samples/reactor/text-block-alignment/readme.md`)
TextBlock alignment
Run cargo run -p reactor-text-block-alignment.
TextBlock::new()
.text("Centered text")
.text_alignment(TextAlignment::Center)text_alignment positions text within the TextBlock's available width. horizontal_alignment
positions the TextBlock itself. The sample compares left, centered, and right text in stretched
controls with a centered, content-sized control. Resize the window to see the difference.
TextBlock and TextBox share Reactor's TextAlignment enum. Canvas has a separate TextAlignment
for DirectWrite; qualify the type or use an import alias when using both crates.
Updating or omitting text_alignment keeps the native TextBlock. Omitting a previously declared
alignment clears its local value and lets WinUI supply the alignment again.
Overview (`crates/samples/reactor/text-box-read-only/readme.md`)
Read-only TextBox
Run cargo run -p reactor-text-box-read-only.
Both fields allow selection and copying without editing. The second uses text_alignment to center
its text inside the stretched control. Resize the window to see the difference.
Overview (`crates/samples/reactor/translation-transition/readme.md`)
Translation transition
Run cargo run -p reactor-translation-transition.
Open and close the panel, or click again while it is moving to reverse direction. Translation is a
visual offset in DIPs; it does not change the space reserved by layout or remove the panel.
Overview (`crates/samples/reactor/virtual/readme.md`)
Virtual task editor
This sample qualifies windows-reactor virtualization with application behavior rather than
static labels. It includes:
- keyed row components with controlled
TextBoxandToggleSwitchvalues; - front insertion, removal, move-to-end, reversal, and a 1,000-item reset;
- direct selection input for each keyed row;
- validation and queued typed focus;
- keyed row effects and cleanup on recycle;
- background loading with scope-owned cancellation;
- conditional row content;
- same-key payload updates and key-changing source resets.
Run it with:
cargo run -p reactor-virtualThe same component model has a feature-gated RecordingRuntime performance driver. Run it in
release mode:
cargo run -p reactor-virtual --bin reactor-virtual-perf `
--features perf --release -- --samples 500The driver measures local editing, broad and redundant parent messages, an unchanged root-component
memo hit, a forced value-equal root recomposition, 32-row recycle/realize batches, background
completion, and a mixed virtual cycle. It reports allocator traffic and median, p95, and p99
Rust-side turn time. It also measures value-equal declaration scaling at 1,000, 10,000, and 100,000
tasks. It does not include WinUI control work, layout, rendering, or presentation.
The live driver adds CompositionTarget::Rendering frame intervals and host/native phase timing:
cargo run -p reactor-virtual --bin reactor-virtual-live-perf `
--features perf --release -- --samples 300
cargo run -p reactor-virtual --bin reactor-virtual-live-perf `
--features perf --release -- --baseline --samples 300The active run moves by 32 virtual indices per frame, alternates selection, edits a
controlled row every six frames, and delivers a background completion every 30 frames. The
baseline opens the same 1,000-item editor without scheduling those actions. Each command writes a
report next to the executable. After the measured frames, the driver also verifies that queued
recycle and source-reset work left no retired WinUI shell tokens. A machine that emits no
composition frames writes an explicit failure report and exits unsuccessfully after 20 seconds.
The durable task title belongs to TaskEditor, not the realized row component. A row component is
retained across key-stable payload updates, but native virtualization or a source reset may recycle
it. Non-empty controlled drafts update that model immediately; a blank validation draft remains
row-local and falls back to the last valid model value after recycling. The recording-runtime test
enters edit mode, checks queued focus, edits a task, explicitly recycles it, reverses the source,
and verifies that its title survives each re-realization with one cleanup per retired row effect.
The buttons provide a deterministic manual stress sequence:
- Select a row, enter edit mode, and change its title.
- Reverse or move the source and confirm the title remains in the task model.
- Add and remove rows at the front.
- Load another 100 rows in the background.
- Reset to 1,000 rows and repeat edits while scrolling.
Overview (`crates/samples/windows/dcomp/readme.md`)
This sample first appeared in Kenny's DirectComposition course on Pluralsight. Check it out to learn all about DirectComposition, Direct2D, and DirectWrite, image decoding, and animation.
Overview (`crates/samples/windows/direct3d12/readme.md`)
Overview (`crates/samples/windows/spellchecker/readme.md`)
Windows SpellChecker
A sample showing how to use the Windows spell-checking API (ISpellCheckerFactory, ISpellChecker)
from the windows crate. It checks a line of text from the command line and prints corrections to
standard out.
Running
Make sure you have Rust installed, and then run the following:
cargo run -- "This is text with a word missspelled!"Overview (`crates/samples/windows/uiautomation/readme.md`)
UI Automation
A simple example using the new UI Automation API. This sample requires Windows 11.
Overview (`crates/tests/libs/reactor/readme.md`)
Reactor tests
test-reactor is the home for the new Cargo-based Reactor tests. It lives alongside the existing
selftest, integration, and performance crates without depending on their harnesses or Reactor'stest feature. Add new cases here and migrate existing coverage individually. Remove an old case
only after its replacement proves the same behavior; retire old helpers when their consumers are
gone.
The crate uses the public Reactor API, direct Windows UI Automation, and the Rust standard library.build.rs generates only the required native bindings into OUT_DIR using windows-bindgen and
stages the host's private Windows App Runtime using windows-reactor-setup.
There are no generated test cases, external runners, or dependencies on the windows crate.
Running
cargo test -p test-reactorOn x64 this runs the automated suite, including live WinUI cases and harness failure checks. It requires
an interactive desktop. Cargo deploys the runtime; no machine-wide runtime installation is needed.
The first build needs network access to download the pinned runtime packages. Five native drag/drop
cases are explicitly ignored; see Opt-in drag/drop coverage. Foreground
or focus failures do not silently skip tests. Do not interact with other applications while the
suite runs. Standard Cargo name filters can select individual cases during development.
The crate also runs in the ordinary workspace test command. Other workspace packages can enable
Reactor's legacy test feature through Cargo feature unification; this fixture still uses only the
public application API and real WinUI. The package-only command covers the default-feature build.
The ui integration target is x64-only. The existing x64 workspace job runs the live suite; x86 and
ARM64 build the fixture and run windows-reactor-setup's filesystem and multiprocess tests as part
of workspace testing. This is an explicit architecture boundary, not a runtime skip when focus
or foreground is unavailable.
Layout
| Location | Purpose |
|---|---|
build.rs |
Native binding generation and self-contained runtime deployment |
src/lib.rs |
Small public-API applications and process-failure probes |
src/main.rs |
Entry point for the isolated WinUI host |
tests/ui/main.rs |
Named behavioral tests driven through UI Automation |
tests/ui/automation.rs |
Scoped lookup, native actions, condition waits, and diagnostics |
tests/ui/menu.rs |
Button-menu callback, content, attachment, and owner lifetimes |
tests/ui/process.rs |
Child ownership, deadlines, shutdown, and harness failure checks |
tests/ui/pointer.rs |
Native mouse injection, owned button cleanup, and physical coordinates |
src/pointer.rs |
Property-only pointer capture and focus fixtures |
src/drag/ |
Property-only drop target and owned native OLE text source |
tests/ui/drag.rs |
Opt-in drag negotiation, policy changes, and cancellation |
The library contains the application fixtures. Cargo's integration-test harness supplies the
host executable's path without filesystem searches or a child mode in the test runner. Keeping the
host separate gives WinUI its own main thread and application lifetime. The binary's test = false
disables its empty unit-test harness, not the behavioral tests.
Each UI case launches the Cargo-built test-reactor executable in a fresh process. The suite
serializes desktop cases even with Cargo's default thread count. UI Automation runs on an MTA
worker, while the test thread watches the child and enforces a 30-second deadline, including blocked
UIA calls. Success requires completed assertions and a successful exit after closing the fixture's
own window. Failure cancels further driver work and terminates the owned child.
| Case | Assertion |
|---|---|
menu_callback_replacement_and_removal |
An open item's native identity survives callback replacement; invoking it reaches the new callback exactly once. A retired item's invocation does not change the displayed count during a 250 ms observation period. |
menu_content_replacement_retires_open_items |
Changing content closes the old popup and creates a new item on the same owner. The old item stays inert before and after opening the replacement. |
menu_reattachment_does_not_revive_retired_items |
Removing and reattaching a menu on the same native owner creates new items without reviving retained providers. |
menu_owner_replacement_retires_open_items |
Replacing the keyed button closes its popup and changes native owner/item identity. A callback update on the replacement preserves its open item and reaches the current callback. |
text_box_native_edit_and_controlled_reset |
UIA Value edits reach component state; declarative reset clears the native value; focused Unicode keyboard input reaches both state and the native TextBox. |
keyed_reorder_moves_only_displaced_children |
Moving the last keyed TextBox to the front keeps every element's native identity and produces the new layout order. The focused TextBox that triggered the reorder is not displaced, so it keeps keyboard focus. |
early_successful_exit_is_rejected |
Exiting with code zero before the driver finishes is a failure, not a passing test. |
child_panic_is_reported |
A child panic produces a failed exit and captured stdout and stderr. |
hung_child_is_terminated |
A ready but unresponsive child times out and is terminated. |
driver_timeout_cancels_worker |
The supervisor deadline cancels a cooperative driver and reaps its child without consuming the cleanup allowance. |
driver_timeout_bounds_blocked_worker_cleanup |
The supervisor reaps the child and returns after its cleanup allowance even when the driver remains blocked. |
pointer_capture_policy_at_mount / pointer_capture_policy_added_to_existing_border |
With only the capture policy, movement outside reaches the pressed Border; after release it reaches the outside surface. |
pointer_focus_policy_at_mount / pointer_focus_policy_added_to_existing_border |
With only the release/focus policy, the Border receives direct native pointer focus after release, not on press. |
pointer_button_is_released_after_driver_failure |
A driver failure while pressing the mouse releases the injected button before the fixture is stopped. |
text_data_transfers_owned_unicode_memory |
The data source rejects unsupported formats and returns independent, nul-terminated UTF-16 allocations that outlive its COM object. This unit test runs normally. |
The driver timeout regressions use the same supervisor as live UI cases, with short private
deadlines and synthetic workers rather than COM calls. Each case releases and joins its worker
before asserting the outcome. Live cases retain the 30-second deadline and five-second cleanup
allowance.
The driver uses exact automation IDs, or exact name and control type for menu items, scoped to the
child's windows. Repeated entries for the same native element count once; distinct matching elements
are an error. Waits inspect current UI state; they do not force layout or synthesize Reactor
callbacks. UIA invocation, Value edits, and keyboard input are separate operations. Keyboard injection
checks the foreground process, focused element, and modifier keys.
Button-menu cases use UIA Invoke to exercise WinUI's menu actions, not synthetic Reactor events.
They retain old invoke providers across content, attachment, and owner changes, check popup closure
including the submenu entry, and invoke retired providers before and after opening a replacement.
Retired invocations must leave both the callback count and the replacement popup unchanged. Current
items must deliver the expected key and callback generation exactly once; callback counts are
observed for 250 ms to detect extra deliveries. These tests cover button menus. Dropdown, menu-bar,
command-bar, and tray surfaces remain in the legacy integration fixture.
Pointer cases use SendInput with physical virtual-desktop coordinates derived from UIA anchor
bounds. The fixture's Border has no UIA peer; short text anchors locate its bare background without
clicking the text itself. Guards check foreground ownership, the window under button input, and
held buttons/modifiers. The supervisor owns button cleanup, so cancellation or a blocked UIA worker
cannot leave cleanup dependent on that worker returning. A case that finishes with an injected
button still pressed fails.
The policy fixtures install no press/release callbacks on the target. Capture and release are
observed through native movement routing, not a stored policy or capture flag. Focus is observed
through the target's direct native focus event; setting focus on the baseline button is a separate
driver action. Dynamic cases omit the policy entirely at mount, exercise that state, and then enable
it while preserving the UIA anchor's identity. They do not set the policy to false initially, since
that setter can itself activate a lazy native subscription.
Cargo captures driver steps and assertion failures. On a condition timeout the driver also prints
the fixture's UI tree. Child stdout and stderr are written to separate files to avoid blocked pipes;
failures report the log directory under the system temporary directory. Successful cases remove
their logs. The parent deadline still applies if reading the UI tree stalls.
Opt-in drag/drop coverage
The native drag/drop cases are preserved from PR #5115, but are not part of the default automated
suite. WinUI does not support this drag/drop path in elevated processes
(microsoft/microsoft-ui-xaml#7690).
GitHub-hosted Windows runners run as administrator. The tests use #[ignore = "..."] so Cargo reports
them as ignored, not passed. Explicitly running them elevated fails before launching a fixture.
No runner-specific skip, token manipulation, or automatic elevation change is used.
Run the policy and failure-cleanup cases from a non-elevated terminal on an idle interactive desktop:
cargo test -p test-reactor --test ui drop_policy -- --ignored
cargo test -p test-reactor --test ui drag_source_is_stopped_after_driver_failure -- --ignoredThe cancellation case also has an unresolved intermittent UIA timeout (0x80131505) after
cancellation. Keeping the source window and its STA alive across drags has not eliminated that
failure. It remains an opt-in regression case, not evidence that cancellation is reliable:
cargo test -p test-reactor --test ui cancelled_drag_can_be_restarted -- --ignored --nocaptureTo run all five opt-in cases, including the unresolved cancellation regression:
cargo test -p test-reactor --test ui -- --ignored| Opt-in case | Assertion |
|---|---|
drop_policy_at_mount / drop_policy_added_to_existing_border |
A callback-free Border accepts native text drags through its policy, including activation after an initially rejected drop. |
drop_policy_changes_during_drag |
Source feedback follows Copy-to-Move policy changes while hovering; switching back before release produces a Copy drop that reads the text. |
cancelled_drag_can_be_restarted |
Three cancelled hovers preserve the source's native UIA identity; another drag then drops successfully. Subject to the unresolved timeout above. |
drag_source_is_stopped_after_driver_failure |
Failure during an accepted OLE hover releases the injected button and reaps the fixture process, including its source thread. |
The source is a native window on a dedicated OLE STA in the fixture process. It receives injected
input and runs DoDragDrop, leaving WinUI free to render policy changes and read dropped data.
It advertises only CF_UNICODETEXT. Assertions require source-reported hover operations, the final
OLE operation, and a text read, not just a Copy cursor. The target has no drag/drop callbacks that
could mask policy-only activation.
The source window and STA live for the fixture session. Each drag owns its data and feedback
objects. Normal shutdown joins the UIA driver before closing the fixture, which stops and joins the
source thread. The OLE callback checks cancellation and a 20-second deadline; the existing parent
supervisor still bounds blocked native calls. No UIA timeout is retried or treated as success.
App-process coverage
The pilot uses compiler instrumentation and the llvm-tools Rust toolchain component, without a
Cargo coverage plugin. In a fresh PowerShell session at the repository root:
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
$target = Join-Path $PWD "target\reactor-ui-coverage"
$profiles = Join-Path $target ("profiles-" + [Guid]::NewGuid())
New-Item -ItemType Directory $profiles | Out-Null
$env:RUSTFLAGS = "-C instrument-coverage"
$env:LLVM_PROFILE_FILE = Join-Path $profiles "%p-%m.profraw"
cargo test -p test-reactor --target-dir $target
$sysroot = rustc --print sysroot
$hostTriple = (rustc -vV | Select-String "^host: ").ToString().Substring(6)
$llvm = Join-Path $sysroot "lib\rustlib\$hostTriple\bin"
$raw = @(Get-ChildItem $profiles -Filter "*.profraw")
& "$llvm\llvm-profdata.exe" merge -sparse $raw.FullName -o "$profiles\ui.profdata"
& "$llvm\llvm-cov.exe" show "$target\debug\test-reactor.exe" `
"-instr-profile=$profiles\ui.profdata" `
'--include-filename-regex=native[\\/]+winui[\\/]+(hosting|events)\.rs$'The unique profile directory prevents earlier runs from contributing counts. Child processes
inherit the profile pattern, and %p-%m separates their output. Report against test-reactor.exe,
not just the integration-test driver: the native Reactor code runs in the fixture. The source filter
above shows the menu callback update and native text setter paths exercised by these cases. This
is a pilot path report, not an overall Reactor coverage percentage or a replacement for the core
coverage job. Close the PowerShell session afterwards to discard the instrumentation environment.
Overview (`crates/tests/libs/reactor_bench/readme.md`)
Reactor benchmark
This benchmark measures Reactor retained updates, component scheduling, lifecycle work,
virtualization, and memory attribution without WinUI layout or rendering.
cargo run -p test-reactor-bench --release --quiet
cargo run -p test-reactor-bench --release --quiet -- --count 4096 --samples 40 --batch 4
cargo run -p test-reactor-bench --release --quiet -- --architecture-gate
cargo run -p test-reactor-bench --profile profiling --quiet -- --retained-memory --count 512The output includes median and p95 update time, allocated bytes and allocation count per update,
retained bytes per object, isolated component updates, and retained component-scope memory.
Component rows also cover isolated and all-consumer typed-context invalidation, same-type updates,
and retained-root type replacement.
The recursive component rows build balanced trees, target the deepest leaf, and report retained
bytes per scope, update latency, allocations, allocated bytes, and mutation count. The timed
updates disable recording-adapter batch validation because that diagnostic intentionally clones
the complete adapter state.
The --architecture-gate mode uses six samples by default and reports:
- Keyed no-change and one-property-change updates across two 50,000-object graphs in one timed
operation. Reactor enforces a 65,536-object limit per graph. - Controlled 513-node memory fixtures that separate declaration construction, retained
graph storage, string properties, events, TreeNode relation shape, and recording-adapter storage. - Root-type replacement and middle-child removal in a 16,384-component graph.
Use --gate-samples or --component-count to override the gate defaults.
The --retained-memory mode releases benchmark scratch buffers after mounting and reports live
retained-graph allocations separately from declarations and adapter state. It prints release
layout sizes and additive slot, relation, child-vector, property/event, virtual, and retirement
storage for keyed and unkeyed flat trees, a deep chain, relation-heavy controls, TreeNode
structural objects, a virtual source, and an active retirement.
No-op allocation, changed-update allocation, retained memory, and large-tree scaling are separate
gates.
Overview (`crates/tests/libs/reactor_integration/readme.md`)
Reactor public integration tests
These isolated executables exercise the public application API without Reactor's test feature.
Build this package separately from privileged selftests and benchmarks so Cargo does not unify
their diagnostic features into this build.
cargo check -p test-reactor-integration --all-targets
cargo run -p test-reactor-integration --bin application_lifecycle -- replacement
cargo run -p test-reactor-integration --bin application_lifecycle -- multiple
cargo run -p test-reactor-integration --bin application_lifecycle -- startup-error
cargo run -p test-reactor-integration --bin application_menu
cargo run -p test-reactor-integration --bin application_menus
cargo run -p test-reactor-integration --bin menu_lifetimes -- dropdown
cargo run -p test-reactor-integration --bin application_resources -- lifecycle
cargo run -p test-reactor-integration --bin application_dispatch -- application
cargo run -p test-reactor-integration --bin application_dispatch -- notification
cargo run -p test-reactor-integration --bin tray_flyout
cargo run -p test-reactor-integration --bin menu_position -- thread-unaware
cargo run -p test-reactor-integration --bin canvas_integration
cargo run -p test-reactor-integration --bin webview_integration
cargo run -p test-reactor-integration --bin window_state
cargo run -p test-reactor-integration --bin window_observers
cargo run -p test-reactor-integration --bin window_theme
cargo run -p test-reactor-integration --bin title_barEach executable owns its application lifetime and watchdog. The lifecycle fixture covers
last-window replacement, multiple-window shutdown, and startup errors. The menu fixture checks
application-lifetime work after its Reactor window closes. The application-resource fixtures cover
declarative window and icon lifetimes, nested dispatch, and menu replacement and selection.
The notification dispatch mode sends interactions and recovery requests to its own callback window
during nested component and application updates, and checks that queued events cannot outlive icon
removal.
The tray-flyout fixture uses an application-owned icon and checks that its menu overlaps the icon's
monitor, extends outside that monitor's work area, and closes during application shutdown.
It measures menu-item bounds through UI Automation rather than the popup window's shadow bounds,
and requires a running Windows shell with a notification area. Companion-crate fixtures exercise
Canvas drawing and WebView initialization. The window-state fixture observes size and icon changes
through the public window callback and Win32 APIs. The window-observer fixture covers callback
replacement without replay, real changes, removal, re-subscription, and independent subscriptions
for color scheme, size, and placement. It also covers removal and re-subscription within one drain,
handoff from a parent to a child, and replacement of the subscribing child. The title-bar fixture
opens a second window from a running application and checks through UI Automation that itsTitleBar right header keeps the same
caption-button inset as the first window.
Run window_theme on an interactive desktop. It temporarily makes its windows topmost without
activation to sample their rendered backgrounds.
Run menu_lifetimes with dropdown, menubar, command, or tray on an interactive desktop.
It uses process-scoped UI Automation and notification messages sent to its own icon. Button-menu
lifetimes are covered by the named Cargo tests in test-reactor.
The menu-position fixture covers pmv2, unaware, system, and thread-unaware DPI modes. It
checks exact physical host bounds and restoration of the caller's thread context, including when
a duplicate menu request is rejected. Run on a display scaled above 100% to exercise coordinate
virtualization.
Use --features self-contained to run with bundled Windows App SDK dependencies. Otherwise,
the Windows App Runtime must be installed. WebView tests also need the WebView2 runtime.
Overview (`crates/tests/libs/reactor_perf/readme.md`)
Reactor stress benchmark
test_reactor_perf compares windows-reactor with the C# ReactorStressPerf.ReactorOptimized workload:
- 70 columns x 70 rows = 4,900
TextBlockcells; - deterministic
.NET Random(42)data and update sequence; - a 33 ms update timer;
- at least one randomly selected cell per tick, controlled by
--percent; - cached cell declarations rebuilt only for changed indices;
- snapshot-driven root component updates;
- phase, frame, allocation, and working-set reporting.
Run the live benchmark in release mode:
cargo run -p test_reactor_perf --release -- --headless --percent 10 --duration 10The report is written next to the executable as StressPerf.Reactor.report.txt. Add --json to
also write StressPerf.Reactor.metrics.json.
The in-app frame counter uses CompositionTarget::Rendering, matching the C# workload. As with the
C# harness, render completion is a better no-admin throughput proxy, and ETW presents are required
for a final user-visible frame-rate comparison.
Overview (`crates/tests/libs/reactor_selftest/readme.md`)
Reactor native self-test
This executable runs the Reactor WinUI adapter inside the Reactor application host. Generated
coverage constructs and clears every schema property and event contract. Handwritten phases cover
controlled input, ordered native events, attachments, images, references, pointer injection,
retirement, TreeView content, ListView data, and virtualization.
cargo run -p test-reactor-selftest --quiet
cargo run -p test-reactor-selftest --quiet -- --headlessA watchdog terminates the process with an error if WinUI hangs or the application fails to exit.
Application lifecycle, menu, Canvas, WebView, and window-state fixtures live intest-reactor-integration. They use the public application
API without enabling Reactor's diagnostic test feature.
Asynchronous phases wait for native readiness rather than assuming a timer tick is sufficient.
The ContentDialog check requests opening before its owner window exists, waits for Opened, then
calls Hide and requires Closed.
Use assertions for failures in component callbacks. Calling process::exit there can run
application teardown while callback state is still borrowed.
CI also runs the self-test and native library tests in debug mode withRUSTFLAGS="-D warnings --cfg windows_cast_diagnostics". check_casts.ps1 checks their captured
output for unexpected same-pointer QueryInterface calls. Its allowlist covers agility checks,
canonical COM identity, activation, and known conversions from IInspectable; these still require
queries even when the returned pointer is unchanged. Review new diagnostics before adding an
exception. A clean result covers only the paths exercised by those tests.
Run every default Reactor, Composition, and WebView sample long enough to catch startup failures:
powershell -File crates\tests\libs\reactor_selftest\run_samples.ps1The script builds each package independently, captures runtime stdout and stderr, requires the
process to remain alive for three seconds, and then closes or terminates that process. Use-RunSeconds, -Family, or -Package to narrow or lengthen a run, and -KeepLogs to retain all
captured output.
Use -Package with -ChangedSince <commit> to run the named packages plus samples changed between
that commit and HEAD. Added files and deleted files within surviving packages count as changes;
removed packages are not run. -List prints the selection without building or launching anything.
The comparison commit must be available locally.
CI
PRs and master pushes run the native self-test once, with cast diagnostics, and retain the native
regressions, public integration fixtures, deployment checks, isolated feature checks, and public
API snapshot. Workspace unit tests and generation checks belong to test.yml and gen.yml.
The smoke gate runs Counter, Gallery, Workbench, the Reactor Composition host, standalone system
Composition, and Reactor WebView, plus changed or new samples. PRs compare against their base commit;
pushes compare against the previous branch tip, including every commit in the push. Each package
is built independently, and GUI processes run serially.
Scheduled and manually dispatched runs also enforce coverage floors, repeat the native self-test,
and launch every sample. The routine gate targets roughly ten minutes; the exhaustive runs can take
longer. CLI tools use pinned cargo install versions. The public API tool has a separate cache saved
immediately after installation, so a later test failure does not discard the compiled tool.
Run the sample selection checks without starting any windows:
pwsh -File crates\tests\libs\reactor_selftest\test_samples.ps1Code Of Conduct (`docs/code_of_conduct.md`)
Code of conduct
This project has adopted the Microsoft Open Source Code of Conduct.
Resources:
- Microsoft Open Source Code of Conduct
- Microsoft Code of Conduct FAQ
- Contact [email protected] with questions or concerns
Contributing (`docs/contributing.md`)
Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will determine whether you need to provide a CLA and
decorate the PR appropriately (for example, with a status check or comment). Follow the bot's
instructions. You only need to do this once across all repositories using our CLA.
This project has adopted the
Microsoft Open Source Code of Conduct. For more
information, see the
Code of Conduct FAQ or contact
[email protected].
Before Contributing
Start by
opening a new issue or commenting on
an existing issue to discuss a feature or bug
fix. This lets maintainers and contributors agree on an approach before you spend time on a pull
request. The pull request template therefore asks you to refer to an existing issue.
Dependencies (`docs/dependencies.md`)
Dependencies
This page tracks every external SDK, header set, metadata file, and runtime the build and tooling
depend on: the version, where it is pinned, how it is obtained, and how the pin is validated. It
covers build/tooling and runtime dependencies (Windows SDK, WDK, WinRT contracts, libclang,
Windows App SDK, WebView2). It does not cover the crates.io Rust dependencies in Cargo.toml,
which are already centralized under [workspace.dependencies].
The model: one owner, validated by running
There is no central version registry. Every external dependency has a single owning tool that
declares the pin as a const, downloads/consumes the artifact at that exact version, and runs in CI
(.github/workflows/gen.yml) - so running the tool proves the pin is current, in one of two ways:
- Generators (
tool-win32,tool-winrt,tool-webview,tool-reactor-metadata,tool-reactor) regenerate
committed artifacts;gen.ymlruns each thengit diff --exit-code, so a stale pin produces a
diff and fails. - Validators (
tool-clang, and guards inside generators) assert invariants and write nothing; a
violation panics loudly, failing the job with a clean tree.
When one crate must track a pin another crate owns, the owner reads the peer's constant straight
from source with helpers::read_str_const and asserts they
agree. Pins are never copied - they are read back and checked. windows-clang stays a clean
libclang library and is not a shared home for SDK/runtime versions.
At a glance
| Dependency | Version | Owner (pin) | Obtained by | Validated by |
|---|---|---|---|---|
| libclang | 22.1.8 |
LIBCLANG_VERSION - crates/libs/clang/src/provision.rs |
download (NuGet: libclang.runtime.win-<arch> + git sparse checkout of LLVM headers) |
tool-clang validator + runtime assert |
| Windows SDK | 10.0.28000.2270 |
SDK_VERSION - crates/tools/win32/src/main.rs |
download (NuGet) | tool-win32 zero-diff regen |
| Windows WDK | 10.0.28000.1839 |
WDK_VERSION - crates/tools/win32/src/km.rs |
download (NuGet) | tool-win32 zero-diff regen |
| SDK Contracts (WinRT) | 10.0.28000.2270 |
CONTRACTS_VERSION - crates/tools/winrt/src/main.rs |
download (NuGet) | tool-winrt zero-diff regen |
| WebView2 SDK headers | 1.0.4078.44 |
WEBVIEW2_VERSION - crates/tools/webview/src/main.rs |
download (NuGet) | tool-webview zero-diff regen |
WinUI / Windows App SDK metadata (.winmd files) |
2.5.1 |
WINDOWS_APP_SDK_VERSION - crates/tools/reactor-metadata/src/main.rs |
download (NuGet) | tool-reactor-metadata zero-diff regen of the committed metadata |
| Windows App SDK runtime | 2.5.1 |
RUNTIME_VER - crates/libs/reactor-setup/src/lib.rs |
download (NuGet) | tool-reactor-metadata guard: == WINDOWS_APP_SDK_VERSION, and reactor.yml matches |
| WebView2 runtime projection | 1.0.4078.44 |
WEBVIEW2_VER - crates/libs/reactor-setup/src/lib.rs |
download (NuGet) | tool-reactor-metadata guard: == WEBVIEW2_VERSION |
| LLVM / libclang (CI) | 22.1.8 |
LIBCLANG_VERSION - crates/libs/clang/src/provision.rs |
download (NuGet) via tool-clang path |
tool-clang: loads the pin and asserts its version |
Toolchain: libclang
The header scrapers (tool-win32, tool-webview) parse C/C++ with libclang. The
version is pinned because clang's macro-capture behavior drifts across majors, which would silently
change the generated metadata.
- Owner:
provision.rsdeclaresLIBCLANG_VERSION = "22.1.8".libclang.dllcomes from thelibclang.runtime.win-<arch>NuGet packages (dotnet/clangsharp, .NET Foundation) fetched at
that version. The paired clang builtin resource headers (needed only for the non-x64 arch
passes, to reconcile the aarch64__prefetchbuiltin) come from a blobless, shallow, sparsegit
checkout ofclang/lib/Headersat the derivedllvmorg-<ver>tag - so the DLL and headers are
the same single-const pin and can never drift. - Obtained: if
LIBCLANG_PATHis unset,ensure_libclangfetches the pinnedlibclang.runtime.win-<arch>package vianuget_package(the shared NuGet global cache, same as
the SDK/WDK/WebView2 pins) and pointsLIBCLANG_PATHat itsruntimes/<rid>/native/; non-x64
passes also fetch the pinned LLVM resource headers viagit.LIBCLANG_PATH/CLANG_RESOURCE_DIRoverride for offline machines. All three scrapers call it, so theirgen.yml
jobs need no LLVM install - they always parse with the pinned22.1.8, in CI and on a fresh
checkout alike. - CI: every workflow self-provisions the pinned libclang from NuGet - no CI job installs LLVM.
Thegen.ymlscrapers callensure_libclang;clippy.ymlloads no libclang at all
(cargo clippynever parses); andtest.yml, whosewindows-clangtests load libclang at
runtime,
exportsLIBCLANG_PATHfrom the same pin via `echo "LIBCLANG_PATH=$(cargo run -q -p tool-clang --
path)""$GITHUB_ENV"
.tool-clang pathprintshelpers::libclang_dir(), keeping theunsafeset_var` off the multithreaded test runner. The Linux CI jobs build code that needs no libclang. - Validated by
tool-clang: fetches, loads, and version-asserts the pin (the same provisioning
the scrapers run). Writes nothing. - To update: bump
LIBCLANG_VERSION- a single const that drives both the NuGet DLL and thellvmorg-<ver>git tag for the headers, so there is nothing else to touch. Runtool-clang(must
pass) and regenerate all metadata. No prebuilt-asset ceiling: the DLL comes from NuGet and the
headers from a git tag, both of which track current LLVM.
Windows SDK, WDK, and WinRT contracts
Each feeds an in-house generator producing a committed .winmd in windows-default. The metadata
is embedded for Rust build tools and remains available as files for external tools. Provenance is
documented in crates/libs/default/readme.md.
windows-bindgen, windows-rdl, and windows-clang depend on windows-default so their builders
can select this metadata without filesystem paths. Binaries linking those crates include both
metadata files.
| Package | Owner (pin) | Produces |
|---|---|---|
Microsoft.Windows.SDK.CPP[.<arch>] |
SDK_VERSION - tool-win32 |
um scrape half of Windows.Win32.winmd |
Microsoft.Windows.WDK.x64 |
WDK_VERSION - tool-win32 |
km scrape half of Windows.Win32.winmd |
Microsoft.Windows.SDK.Contracts |
CONTRACTS_VERSION - tool-winrt |
Windows.winmd |
- Obtained:
windows_clang::nuget_package(id, version)restores from the NuGet global cache or
downloads from nuget.org (NUGET_PACKAGESoverrides the cache). tool-win32runs both scrapes, so the WDK km scrape reuses the sameSDK_VERSIONheaders as
the um scrape (they share the crate);WDK_VERSIONis a separate pin inkm.rs.- The "marketing" include/lib folder nested in each package (e.g.
10.0.28000.0) is derived
from the version viahelpers::marketing_dir(first three components +.0), so the version is
the single edit - never a second folder constant to keep in sync. - To update: bump the owning constant, run
cargo run -p tool-<win32|winrt>, and commit the
regenerated.rdlsnapshot(s) and.winmd.
CONTRACTS_VERSION happens to share the 10.0.28000 build number with SDK_VERSION, but it is a
distinct NuGet package with its own pin - the two are not coupled and can diverge.
WebView2
WebView2 ships only C/C++ SDK headers, so windows-webview is scraped from them. The WinRT Core
metadata and runtime projection DLL are separate artifacts.
| Artifact | Owner / location | Used by |
|---|---|---|
WebView2.h, WebView2Interop.h |
WEBVIEW2_VERSION - tool-webview (downloaded, not vendored) |
tool-webview -> webview/src/bindings.rs |
Microsoft.Web.WebView2.Core.winmd |
regenerated into crates/tools/reactor-metadata/winmd/ by tool-reactor-metadata at WEBVIEW2_VERSION |
tool-webview, tool-reactor |
Runtime projection (Core.dll), WEBVIEW2_VER |
crates/libs/reactor-setup/src/lib.rs |
self-contained apps |
| Evergreen runtime | .github/workflows/webview.yml |
CI test host |
- Headers are downloaded, not vendored:
tool-webviewfetches the pinned NuGet package vianuget_packageand parses the headers from it. A bump is a one-lineWEBVIEW2_VERSIONedit. - Pinned libclang: like
tool-win32,tool-webviewcallsensure_libclang+assert_libclang_version, so it parses with the exact pinned22.1.8(itsgen.ymljob needs no
LLVM install - only the SDK include paths for the system headersWebView2.hpulls in). - Runtime projection: bump
WEBVIEW2_VERinreactor-setup; it must equalWEBVIEW2_VERSION-tool-reactor-metadataasserts this. Core.winmdis refreshed into the shared Reactor metadata bytool-reactor-metadataatWEBVIEW2_VERSION(see below).
For the full pipeline and COM<->WinRT bridge, see windows-webview.
WinUI / Windows App SDK
windows-reactor is generated from WinUI / Windows App SDK .winmd; windows-reactor-setup stages
the matching runtime so reactor apps run. Metadata and runtime are two faces of one release, tied to
a single number.
Metadata is regenerated, not hand-copied. tool-reactor-metadata ownsWINDOWS_APP_SDK_VERSION = "2.5.1". On every run it downloads the umbrellaMicrosoft.WindowsAppSDK metapackage at that version, reads the exact component versions
(Foundation / InteractiveExperiences / WinUI) pinned in its nuspec, downloads each component, and
copies their .winmd - plus Microsoft.Web.WebView2.Core.winmd at WEBVIEW2_VERSION - into the
committed metadata at crates/tools/reactor-metadata/winmd/. gen.yml re-runs the tool and fails
on any diff, so the metadata provably matches the pin. (extras.winmd in that directory is
generated by tool-reactor-metadata from Windows.Win32.winmd, not a package.) The metadata stays
committed because tool-reactor, tool-webview, and tool-composition read it.
| Artifact | Owner / location | Used by |
|---|---|---|
WinUI / Windows App SDK .winmd + WebView2.Core.winmd |
regenerated into crates/tools/reactor-metadata/winmd/ at WINDOWS_APP_SDK_VERSION |
tool-reactor-metadata, tool-reactor, tool-webview, tool-composition |
Microsoft.WindowsAppSDK.Runtime, RUNTIME_VER |
crates/libs/reactor-setup/src/lib.rs |
app runtime deploy |
app.manifest, runtime.txt |
crates/libs/reactor-setup/assets/ (committed) |
runtime staging |
| Runtime installer | .github/workflows/reactor.yml |
CI test host |
windows-reactor-setup is a published runtime helper with no generated artifact, so its pins can't
be proven by regen. Instead tool-reactor-metadata guards them on every run, asserting (and
failing loudly on drift) that:
WINDOWS_APP_SDK_VERSION(metadata) equalsRUNTIME_VER(runtime) - one number drives both;WEBVIEW2_VERequalsWEBVIEW2_VERSION, so the staged WebView2 runtime matches the ABI the
bindings target; andreactor.yml's installer URL installsRUNTIME_VER(.../windowsappsdk/<major.minor>/<ver>/),
so CI's self-tests exercise the runtime apps ship.To update metadata + runtime: bump
WINDOWS_APP_SDK_VERSION(tool-reactor-metadata) andRUNTIME_VER(reactor-setup) together, update thereactor.ymlinstaller URL, then runcargo run -p tool-reactor-metadataand commit the refreshed metadata. The guard enforces the
version agreement; the regen enforces the metadata.assets/app.manifestis a generated activation asset with no committed generator -app.manifesttransforms the App SDKpackage.appxfragmentfiles into SxS fusion format (source
versions in its header). It is forward-compatible, so refreshed only when the reactor control set
needs newly-moved classes, not on every bump.resources.priis staged from the App SDK runtime package only for self-contained deployment viaas_self_contained; framework-dependent staging does not copy it.
See windows-reactor and
windows-reactor-setup.
Download mechanisms
Two independent NuGet paths, both using https://www.nuget.org/api/v2/package/{id}/{version}:
windows_clang::nuget_package- used by the scraping/codegen tools (tool-win32,tool-winrt,tool-webview,tool-reactor-metadata). Restores into the NuGet global cache
(NUGET_PACKAGESoverrides), else downloads via bundledcurl/tar. Layout-agnostic - each
caller indexes the subtree it needs.reactor-setup's staging - runs in thebuild.rsof every consuming app to stage the App SDK
runtime and WebView2 projection. Dependency-free (std + thecurl.exe/tar.exeshipped with
Windows), which is why it does not reusenuget_package.
The `tool-*` / pin pairing
| Tool | Proves | How |
|---|---|---|
tool-win32 |
SDK_VERSION, WDK_VERSION |
zero-diff regen of Windows.Win32.winmd (um + km scrapes and their merge) |
tool-winrt |
CONTRACTS_VERSION |
zero-diff regen of Windows.winmd |
tool-webview |
WEBVIEW2_VERSION |
zero-diff regen of webview/src/bindings.rs |
tool-clang |
LIBCLANG_VERSION (drives NuGet DLL + llvmorg-<ver> header tag) |
pure-check assertion |
tool-reactor-metadata |
WINDOWS_APP_SDK_VERSION; reactor-setup sync |
zero-diff regen of the winmd files; guard reads reactor-setup constants |
tool-reactor |
Reactor schema and WinUI projection | zero-diff regen of declarations, native code, bindings, canvas bridge bindings, and live coverage |
All cross-file reads go through helpers::read_str_const, so each pin is declared once by its owner
and read back everywhere else.
Security (`docs/security.md`)
Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include Microsoft, Azure, DotNet, AspNet, Xamarin, and many more.
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's definition of a security vulnerability, please report it to us as described below.
Reporting Security Issues
Please do not report security vulnerabilities through public GitHub issues.
Instead, please report them to the Microsoft Security Response Center (MSRC) at https://msrc.microsoft.com/create-report.
If you prefer to submit without logging in, send email to [email protected]. If possible, encrypt your message with our PGP key; please download it from the the Microsoft Security Response Center PGP Key page.
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at microsoft.com/msrc.
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
- Full paths of source file(s) related to the manifestation of the issue
- The location of the affected source code (tag/branch/commit or direct URL)
- Any special configuration required to reproduce the issue
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code (if possible)
- Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our Microsoft Bug Bounty Program page for more details about our active programs.
Preferred Languages
We prefer all communications to be in English.
Policy
Microsoft follows the principle of Coordinated Vulnerability Disclosure.
Crates Cppwinrt (`docs/crates/cppwinrt.md`)
cppwinrt
The C++/WinRT compiler, bundled for use from Rust.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
cppwinrt packages the C++/WinRT compiler so it can be
invoked from a Rust build. It is a thin wrapper that runs the bundled cppwinrt.exe with the
arguments you provide, returning its output. This is primarily useful for interop scenarios that
also generate C++/WinRT projection headers.
Rust code should prefer a focused crate or windows-bindgen implementation support. Binary
applications can use the broad windows projection when convenient. Use cppwinrt
when a Rust build also compiles C++ that consumes or implements WinRT types.
Start with the crate README for setup and the help
invocation. The bundled executable runs only on Windows.
First workflow: generate headers for a C++ build
- Produce or obtain the component winmd.
- Choose an output directory owned by the build, normally
OUT_DIR. - Invoke
cppwinrt::cppwinrtwith-infor the component metadata and any reference metadata,
then-outfor the include directory. - Add that directory to the C++ compiler's include paths.
- Link the libraries required by the generated C++ and the component.
crates/samples/robot/component_cpp/build.rs is the component example. It compilessrc/robot.rdl with windows-rdl, passes robot.winmd and the default metadata
directory to C++/WinRT, then compiles component.cpp as C++20. The matchingrobot/client_cpp/build.rs consumes the component's winmd and compiles a C++ client.
Input and output model
cppwinrt(args) accepts any iterator of values convertible to OsStr, so paths do not need UTF-8
conversion. The arguments are passed unchanged to the bundled compiler. On success the function
returns standard output as a String; on failure it panics with standard error.
The wrapper copies cppwinrt.exe to a unique temporary path for each call, runs it synchronously,
and removes the executable afterward. C++/WinRT itself writes the requested projection output.
Call cppwinrt(["-help"]) for the option set supported by the bundled compiler version.
Integration and pitfalls
windows-rdlcan compile a component's reviewable RDL contract to the winmd consumed here.windows-defaultcontains the standard winmd files, but C++/WinRT expects input paths. The robot
samples pass the crate directory containing those files.windows-bindgenis the Rust projection counterpart when both Rust and C++ consume one contract.- Gate build-script use to Windows with the MSVC environment when the build also invokes MSVC.
- Add
rerun-if-changeddirectives for component metadata and C++ sources. Generated headers alone
do not tell Cargo when to rerun the build script. - The wrapper panics on compiler failure and does not return a structured status. Supply only
validated arguments and let the build fail visibly. - Do not assume options from a newer standalone C++/WinRT release. Check the bundled compiler's
-helpoutput.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use cppwinrt.
How it's built
Packages the prebuilt cppwinrt.exe. src/bindings.rs is generated by tool-bindings fromcrates/tools/bindings/src/cppwinrt.txt and supplies the CoCreateGuid call used for temporary
directory names.
Testing
Run cargo test -p cppwinrt; see also the workspace test crates.
Crates Windows Animation (`docs/crates/windows-animation.md`)
windows-animation
A safe wrapper around the Windows Animation Manager (
IUIAnimationManager).
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
When to use it
Use windows-animation when a Windows application needs time-based interpolation for values that
it will draw or apply itself. It works well with immediate-mode rendering: advance the manager on
each frame, read each Variable, and use the values in a canvas, Direct2D, Direct3D, or
DirectComposition scene.
The crate does not create a timer, request frames, draw content, or update a UI property. For
retained WinUI visuals in windows-reactor, Reactor's Composition
transitions usually fit better because the compositor owns their frame updates.
Prerequisites
Initialize COM on the thread that creates the animation objects. For a console or rendering thread,windows_core::init_mta() is suitable, as shown by the samples. A UI thread may use its existing
apartment. Use one monotonic time origin for scheduling and updates.
The README contains dependency setup and a minimal storyboard. The workflow below focuses on
integrating animation into a real frame loop.
First workflow: drive a rendered property
- Create one
Managerand oneTransitionLibrary. - Create a
Variablefor each scalar property, using the current rendered value as its initial
value. - Build a transition and schedule it at an absolute time from the application's animation clock.
- On every requested frame, sample that clock and call
Manager::update(time)once. - Read all variables, draw or apply the values, and request another frame while animation remains
active.
For example, the per-frame portion of a renderer can be kept separate from transition setup:
use windows_animation::{Manager, Result, Variable};
fn draw_frame(manager: &Manager, x: &Variable, now: f64) -> Result<f64> {
manager.update(now)?;
let x = x.value()?;
// Use `x` in the renderer for this frame.
Ok(x)
}now is an absolute timestamp in the same time domain used when scheduling, not elapsed time since
the previous frame. Supplying deltas makes the animation clock stall or move incorrectly.
Core API and lifecycle
Manager coordinates variables, transitions, and storyboards. Reuse it for values updated from
the same frame clock. Variable holds the current interpolated f64. TransitionLibrary creates
transition descriptions. Transition and Storyboard are retained COM-backed values and can be
cloned.
There are two scheduling paths:
Manager::schedule_transitionapplies one transition to one variable at an absolute start time.Manager::create_storyboardcreates a group that can coordinate several variables or sequence
transitions before scheduling the group.
Keep the Manager and every Variable needed by rendering alive for the animation lifetime.
Propagate the windows_core::Result from creation, scheduling, updates, and value reads; COM
initialization and invalid transition parameters are reported through those results.
Transition choices
| API | Use it for |
|---|---|
linear(duration, final_value) |
Constant-rate movement to a value. |
accelerate_decelerate(...) |
Ease-in and ease-out movement. |
instantaneous(final_value) |
A scheduled jump without interpolation. |
Acceleration and deceleration are ratios of total duration and must sum to <= 1.0. Durations and
final values use f64.
For direct scheduling:
use windows_animation::*;
fn retarget(manager: &Manager, library: &TransitionLibrary, x: &Variable, now: f64)
-> Result<()>
{
let transition = library.accelerate_decelerate(0.25, 320.0, 0.3, 0.3)?;
manager.schedule_transition(x, &transition, now)
}Sequencing with storyboards
Storyboard::add_transition adds a transition and returns a Keyframe marking its end.add_transition_at_keyframe starts another transition at that point. A keyframe can coordinate a
later transition on the same variable or another variable.
Build the entire storyboard before calling schedule(start_time). Its time is absolute in the
manager's clock domain. The storyboard sample sequences a rise and fall on one variable; the
same pattern can start a fade after a movement finishes.
Frame-loop and error pitfalls
- Update a manager once per frame before reading its variables. Reading first returns the value
from the previous update. - Use a monotonic high-resolution clock. Wall-clock changes must not move animation time backward.
- Schedule and update with the same time origin.
0.0is valid for a deterministic offline
timeline, while a live app commonly uses seconds since renderer startup. - The crate does not report whether more frames are required. The application owns invalidation
and its stop condition. - Do not block the UI thread between updates in an interactive application; missed updates appear
as skipped visual frames even though interpolation remains time-correct. - Treat a failed creation, transition, schedule, update, or value read as an application error.
Do not continue rendering with a stale value unless that is an explicit fallback.
DirectComposition
Variable::copy_curve copies the variable's curve to an object implementingIDCompositionAnimation. DirectComposition can then evaluate it on its own composition thread,
without the application reading Variable::value each frame.
This API targets Win32 DirectComposition, not the Windows.UI.Composition engine used by WinUI and
Reactor. The two animation object models are not interchangeable.
Samples
Run the headless sample with:
cargo run -p animation-storyboardThe sample schedules one transition directly and chains two more through a storyboard keyframe. It
initializes COM explicitly and prints deterministic values using the absolute-time update pattern.
The canvas/clock sample applies the same API in a live
per-frame rendering loop.
Internal documentation
This section is for contributors to windows-animation.
src/bindings.rs is generated by tool-bindings fromcrates/tools/bindings/src/animation.txt. The hand-written Manager, TransitionLibrary,Storyboard, Transition, Keyframe, and Variable wrappers expose the selected Windows
Animation Manager 2 interfaces.
Manager::new and TransitionLibrary::new create their COM classes withwindows_core::create_instance.
Manager methods delegate scheduling and updates to IUIAnimationManager2. Storyboards add an
explicit keyframe after each transition so the returned opaque Keyframe can anchor another
transition. Variable::copy_curve casts the supplied interface to IDCompositionAnimation before
copying the curve.
Keep the generated filter and the hand-written surface aligned. The headless examples are also the
clearest end-to-end checks of COM creation, direct scheduling, keyframe sequencing, and updates.
Crates Windows Bindgen (`docs/crates/windows-bindgen.md`)
windows-bindgen
Generate Rust bindings for the Windows APIs your project needs.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
Use windows-bindgen to generate a focused binding set from Windows metadata (.winmd).
It includes default Win32 and WinRT metadata and can also read custom metadata. For broad
pre-generated bindings, use windows or windows-sys.
The README shows dependency setup, generation frombuild.rs, and inclusion of the generated file. This guide covers API selection and generation
options.
Generate bindings
Configure the builder, then call write():
windows_bindgen::builder()
.output("src/bindings.rs")
.filters(["GetTickCount", "GetTickCount64"])
.flat()
.sys()
.write();The command-line-style adapter does the same thing:
windows_bindgen::bindgen([
"--out", "src/bindings.rs",
"--flat",
"--sys",
"--filter", "GetTickCount", "GetTickCount64",
]);Both forms require an output path and at least one include filter. Paths are relative to the
generator's working directory.
For a published library, run generation from a separate tool and commit src/bindings.rs.
Consumers then need only the generated code's runtime dependencies. Applications can instead
generate into OUT_DIR from build.rs, as shown in the README.
Select APIs
Start with the function, constant, or type name in the Windows API documentation. For example,.filter("GetTickCount") selects GetTickCount. Include any A or W suffix
in the metadata name; filters do not expand C preprocessor aliases.
Signature dependencies are included automatically. You do not need to list every parameter and
return type. Select an interface or its members explicitly when you also need to call its methods.
The bundled Win32 metadata places APIs in Windows.Win32. Bare names such as GetTickCount and
qualified names such as Windows.Win32.GetTickCount both work. The subsystem paths used bywindows and windows-sys package features, such as Win32_System_Com, are not metadata
namespaces: do not turn them into filters such as Windows.Win32.System.Com.
Filters accept dots or :: between path segments and {...} for grouped names:
| Filter | Selects |
|---|---|
GetTickCount |
A function by name. |
HWND |
A type by name. |
Windows.Win32.GetTickCount |
A function with its metadata namespace. |
Windows::Win32::{GetTickCount, GetTickCount64} |
A group of functions. |
Windows.Foundation |
A WinRT namespace and its children. |
Windows.Storage.Streams.IDataReader::ReadByte |
One interface method. |
Windows.Storage.Streams.IDataReader::{ReadByte, ReadUInt32} |
A group of methods. |
Windows.Storage.Streams.IDataReader::ByteOrder |
A property's getter and setter. |
Windows.Storage.Streams.IDataReader::{} |
The interface type without callable methods. |
Bare names search all input namespaces; qualify a name when you need to distinguish matching
definitions. For custom metadata, use the namespaces declared in that metadata.
Interface shells do not expose iteration. Iteration helpers require First, or Current,HasCurrent, and MoveNext for an iterator.
For runtime classes, select the instance, static, or factory methods you need. For example,Windows.Foundation.Uri::CreateUri selects a factory method; a class name alone does not select
its factory methods.
Member filters select methods on a type, not free functions in a subsystem namespace.
Property and event names select their accessor pairs. For type and namespace selections, prefix a
filter with ! to exclude it. More specific rules take precedence; exclusions win ties regardless
of argument order. Prefer narrow filters to generating a namespace and removing items from it.
Keep longer filter lists in a file
Create apis.txt with one filter per line:
// APIs used by this project.
GetTickCount
GetTickCount64Use .filter_file("apis.txt") or --filter-file apis.txt. Blank lines and lines beginning with// are ignored. A file containing the entire argument list can be read with --etc.
Choose the output
Style controls the generated API. Layout controls where it is written.
| Builder option | Argument | Use |
|---|---|---|
| Default style | No flag | Rich wrappers using windows-core. |
sys() |
--sys |
Raw bindings using windows-link. |
minimal() |
--minimal |
Bindings for hand-written wrappers, without most conveniences. |
| Default layout | No flag | Nested Rust modules matching metadata namespaces. |
flat() |
--flat |
One flat file, suitable for a private bindings module. |
package() |
--package |
A Cargo package with namespace files and features. |
sys and minimal are mutually exclusive, as are flat and package. Narrow filters are needed
in every style. Generated APIs may also use focused support crates such as windows-numerics;
add the dependencies referenced by the generated source.
Bit-field accessors
For native bit fields, default and minimal bindings provide getters and setters over the backing
integer fields. Sys bindings keep the backing fields without accessors.
Common options
The builder reference documents all methods, and bindgen lists the command-line-style
arguments.
| Builder method | Argument | Purpose |
|---|---|---|
input(path) |
--in path |
Read a custom .winmd file or directory. |
input_default() |
--in default |
Add the bundled Windows metadata. |
output(path) |
--out path |
Set the output file or package directory. |
filter(name) / filters(names) |
--filter names... |
Select APIs. |
filter_file(path) |
--filter-file path |
Read filters from a text file. |
implement(name) |
--implement name |
Generate interface implementation traits. |
implement_all() |
--implement |
Generate implementation traits for all interfaces in scope. |
derive(name) |
--derive name |
Add a derived trait to generated types. |
rustfmt(options) |
--rustfmt options |
Pass comma-separated formatter configuration. |
extern_fns() |
--extern |
Use extern declarations instead of link macros with sys. |
compose(name) |
--compose name |
Select a WinRT class for composition with minimal. |
dead_code() |
--dead-code |
Use pub(crate) visibility to detect unused callable bindings. |
For compose, also filter the class and its composable factory methods, and select its override
interfaces with implement.
With no explicit input, generation uses the bundled metadata. Adding a custom input replaces
that default; add input_default() if it also needs Windows definitions. Compile RDL withwindows-rdl, or scrape headers with windows-clang,
before passing the resulting .winmd to bindgen.
Formatting uses rustfmt from PATH and is best effort: unavailable or failed formatting leaves
the output unformatted without a warning. rustfmt("max_width=120") sets configuration, not an
executable path. Install rustfmt when generating checked-in source.
Samples
| Sample | Shows |
|---|---|
| VSS backup | Rich COM bindings in OUT_DIR. |
| Context | Flat sys bindings. |
| Robot component | Custom RDL and implementation support. |
| Robot client | Custom and default metadata together. |
Contributing
The generator reads metadata through windows-metadata.test_bindgen contains the fixtures; run cargo test -p test_bindgen andcargo test -p windows-bindgen. After changing generation logic, run the affected tool-*
generators and inspect the output diffs. tool-bindings generates focused library bindings;tool-package generates the published windows and windows-sys packages.
Crates Windows Canvas (`docs/crates/windows-canvas.md`)
windows-canvas
Safe 2D drawing over Direct2D, Direct3D 11, DXGI, DirectWrite, and WIC.
When to use it
Use Canvas for custom 2D graphics, charts, diagrams, image processing, text layout, geometry hit
testing, or animation. It is a drawing API rather than a control toolkit. Combine it with Reactor
when the app also needs WinUI controls.
Use Composition when the scene is primarily a retained tree of visuals that Windows animates and
composes. Use Canvas when the app draws pixels and paths for each requested frame. The two can be
combined by drawing Canvas content into a composition surface.
Canvas is host-neutral by default. Enable system for the typed windows-window swap-chain helper
and system Composition, or reactor for Reactor controls and lifted Composition. Enablecomposition with the selected host to draw into Composition surfaces. The host adapters can
coexist when composition is not enabled; the Composition backends cannot.
The basic idea
Canvas is an immediate-mode drawing API. The application receives a drawing session and describes
the current frame with commands such as clear, fill_rect, draw_text, and draw_bitmap.
begin a frame -> issue drawing commands -> finish the frame -> present itThere is no retained scene tree. If a shape should still be visible in the next frame, draw it
again. Keep application state - points, colors, text, images - in normal Rust data and turn that
state into drawing commands.
The simplest host is Reactor. It creates the graphics device and swap chain, tracks size and display
scale, and handles device recovery. Enable the feature in Cargo.toml:
windows-canvas = { version = "0.100.0", default-features = false, features = ["reactor"] }
windows-reactor = "0.100.0"Your first canvas
canvas draws once when the surface is ready and again when its size or display scale changes:
use windows_canvas::*;
use windows_reactor::App;
fn draw(ctx: &DrawContext) -> Result<()> {
ctx.clear(ColorF::DARK_SLATE_BLUE);
let brush = ctx.create_solid_brush(ColorF::CORNFLOWER_BLUE)?;
let center = Vector2::new(ctx.width / 2.0, ctx.height / 2.0);
let radius = ctx.width.min(ctx.height) * 0.3;
ctx.fill_ellipse(&Ellipse::circle(center, radius), &brush);
Ok(())
}
fn main() -> Result<()> {
App::run(canvas(draw))
}The callback receives a DrawContext. Its width and height are the available size in
device-independent pixels. It dereferences to DrawingSession, so drawing methods and resource
creation are available directly on ctx.
Clear the surface, or fully cover it, unless keeping pixels from the previous frame is intentional.
Draw shapes with brushes
A brush supplies paint. Create it once within a frame and reuse it for as many operations as
needed:
let brush = ctx.create_solid_brush(ColorF::CORNFLOWER_BLUE)?;
ctx.fill_rect(&Rect::from_xywh(20.0, 20.0, 160.0, 100.0), &brush);
ctx.draw_ellipse(
&Ellipse::circle(Vector2::new(260.0, 100.0), 60.0),
&brush,
4.0,
);
brush.set_color(ColorF::WHITE);
ctx.draw_line(
Vector2::new(20.0, 160.0),
Vector2::new(320.0, 160.0),
&brush,
2.0,
);Rect, RoundedRect, and Ellipse are lightweight geometry values. Use fill_* for a solid
interior and draw_* for an outline. Gradient brushes use the same drawing methods.
Apply a temporary transform
with_transform applies translation, rotation, or scale only while its closure runs, then restores
the previous transform:
let transform =
Matrix3x2::rotation_around(15.0, Vector2::new(ctx.width / 2.0, ctx.height / 2.0));
ctx.with_transform(&transform, || {
ctx.fill_rect(
&Rect::from_xywh(ctx.width / 2.0 - 80.0, ctx.height / 2.0 - 40.0, 160.0, 80.0),
&brush,
);
});Scoped transforms make helper functions easier to combine because one drawing operation cannot
leave a transform behind for the next one.
Build a custom path
For a polygon, PathBuilder::polygon is the shortest route:
let points = [
Vector2::new(160.0, 40.0),
Vector2::new(280.0, 240.0),
Vector2::new(40.0, 120.0),
Vector2::new(280.0, 120.0),
Vector2::new(40.0, 240.0),
];
let star = PathBuilder::new(ctx.device())?.polygon(points)?;
let brush = ctx.create_solid_brush(ColorF::new(1.0, 0.8, 0.0, 1.0))?;
ctx.fill_path(&star, &brush);Use begin, line_to, and bezier_to when the path needs individual segments:
let curve = PathBuilder::new(ctx.device())?
.begin_hollow(Vector2::new(40.0, 160.0))
.bezier_to(
Vector2::new(120.0, 20.0),
Vector2::new(240.0, 300.0),
Vector2::new(320.0, 160.0),
)
.end_open()
.build()?;
ctx.draw_path(&curve, &brush, 3.0);Paths also support bounds and fill or stroke hit testing, which is useful for diagrams and editors.
Draw text
For ordinary text, create a TextFormat and draw into a rectangle:
let format = TextFormat::new("Segoe UI", 32.0)?
.with_alignment(TextAlignment::Center)
.with_paragraph_alignment(ParagraphAlignment::Center);
let brush = ctx.create_solid_brush(ColorF::WHITE)?;
ctx.draw_text(
"Hello, Canvas!",
&format,
&Rect::new(0.0, 0.0, ctx.width, ctx.height),
&brush,
);Use TextLayout when text is drawn repeatedly or needs measurement, reflow, caret bounds, or hit
testing. Cache the layout and rebuild it when its text, format, size, or device changes. Thetext_layout sample shows that pattern.
Load and draw an image
load_bitmap decodes an image file:
let bitmap = ctx.load_bitmap("assets/photo.png")?;
let destination = Rect::from_xywh(20.0, 20.0, bitmap.width(), bitmap.height());
ctx.draw_bitmap(&bitmap, &destination, 1.0);For generated image data, create_bitmap accepts tightly packed premultiplied BGRA pixels:
let bitmap = ctx.create_bitmap(&pixels, width, height)?;
ctx.draw_bitmap(
&bitmap,
&Rect::from_xywh(0.0, 0.0, width as f32, height as f32),
1.0,
);Bitmaps, brushes, paths, and text layouts depend on the graphics device. Creating them inside the
draw callback is the easiest starting point. Cache expensive resources only when profiling shows
the need, and rebuild cached resources when ctx.device_changed() is true.
Redraw when state changes
Use canvas when content changes only because the surface was created or resized. Useanimated_canvas for motion that needs every display frame:
animated_canvas(|ctx| {
draw_frame(ctx, elapsed_time())
})For input-driven content, use canvas_invalidated. Store an Invalidator with the component,
change the drawing data in update, and request one new frame:
struct Sketch {
points: Rc<RefCell<Vec<Vector2>>>,
invalidator: Invalidator,
}
fn update(&mut self, point: Vector2, _context: &ComponentContext<Self>) {
self.points.borrow_mut().push(point);
self.invalidator.invalidate();
}
fn draw_points(ctx: &DrawContext, points: &[Vector2]) -> Result<()> {
ctx.clear(ColorF::BLACK);
let brush = ctx.create_solid_brush(ColorF::WHITE)?;
for &point in points {
ctx.fill_ellipse(&Ellipse::circle(point, 3.0), &brush);
}
Ok(())
}
fn view(&self, _input: &(), _context: &mut ViewContext<Self>) -> View {
let points = Rc::clone(&self.points);
canvas_invalidated(&self.invalidator, move |ctx| {
draw_points(ctx, &points.borrow())
})
}Invalidation keeps the app idle between changes. Do not use continuous animation for a chart,
drawing, or diagram that changes only after input.
Drive a standalone swap chain
Without Reactor, the application creates a GpuDevice and SwapChain and owns the frame loop. The
essential sequence is:
use windows_canvas::*;
fn draw_frame(chain: &mut SwapChain) -> Result<bool> {
let session = chain.begin_draw()?;
session.clear(ColorF::DARK_SLATE_BLUE);
let brush = session.create_solid_brush(ColorF::CORNFLOWER_BLUE)?;
session.fill_rect(&Rect::from_xywh(20.0, 20.0, 200.0, 120.0), &brush);
drop(session);
chain.present()
}The drawing session borrows the swap chain, so finish and drop it before present. A standalone
host must also resize the swap chain, update display scale, schedule frames, and recreate the
device, swap chain, and dependent resources when present returns Ok(false). Thestandalone sample connects those pieces towindows-window. Its resize callback redraws the existing swap chain because the outer render loop
is suspended while Windows runs its modal interactive-sizing loop.
What to read next
| Sample | What it shows |
|---|---|
circles |
Drawing, animation, and resize handling |
clock |
Continuous animation |
Browse crates/samples/canvas for focused drawing and hosting
examples.
Internal documentation
The remainder of this page describes how the crate is built and maintained. Applications do not
need it to use Canvas.
Architecture
The safe wrappers are hand-written and include GpuDevice, SwapChain, DrawingSession,
geometry, brushes, text, bitmaps, effects, and render targets.
GpuDevice owns the shared Direct3D, Direct2D, DXGI, DirectWrite, and WIC objects. A swap chain
owns one D2D device context and is rendered on its owning thread. WARP allows tests and callers to
render without a physical GPU.
device_lost.rs classifies D2D and DXGI loss codes. Standalone callers perform recovery. The
Reactor host rebuilds its device, swap chain, and cached state after loss.
The Reactor integration lives behind this crate's reactor feature. The dependency direction iswindows-canvas[reactor] -> windows-reactor. Canvas owns rendering resources and receives only
typed panel metrics, rendering notifications, deferred frame callbacks, and attachment completion
through ElementRef<SwapChainPanel>.
The system feature enables windows-window and GpuDevice::create_swap_chain_for_window.
Reactor-only builds do not compile windows-window; raw create_swap_chain_for_hwnd remains
available without either host.
Continuous mode uses WinUI's CompositionTarget::Rendering for its normal animation clock. Layout
and scale changes also request a coalesced dispatcher frame because composition rendering can be
delayed while WinUI processes interactive resize input. Demand mode stays idle until layout, scale,
or Invalidator requests a frame.
Panel binding, swap-chain, and attachment-request generations reject stale completion callbacks.
Resizing buffers preserves the existing panel attachment; only panel replacement or swap-chain
rebuilding requires SetSwapChain. Canvas::on_error reports initialization, attachment, resize,
drawing, presentation, and failed recovery through IntegrationError.
CanvasImageSource uses a borrowed drawing session over a WinUI SurfaceImageSource.CanvasCompositionExt uses a borrowed drawing session overICompositionDrawingSurfaceInterop. Both pair every successful BeginDraw with EndDraw,
including panic cleanup.
If a composition-surface draw reports device loss, create a replacement GpuDevice, callGpuDevice::replace_graphics_device to rebind the existing CompositionGraphicsDevice, and retry
the draw. The Composition surfaces and visuals remain in place during recovery.
Code generation
src/bindings.rs is generated by tool-bindings fromcrates/tools/bindings/src/canvas.txt. It contains minimal flat bindings for Direct2D,
Direct3D 11, DXGI, DirectWrite, and WIC, plus referenced WinRT numerics types.
src/reactor_bindings.rs is generated by tool-reactor for the WinUI bridge. Generated files are
committed and must not be edited by hand. After changingcrates/tools/reactor/src/canvas.txt or the generator, run the corresponding tool and verifycargo check -p windows-canvas --quiet.
Testing
Canvas tests use the WARP software rasterizer and do not require a physical GPU. The integration
suite is in test_canvas:
cargo test -p test_canvasUse RenderTarget for off-screen drawing tests. Repeated presents on a composition swap chain
without a visual consumer can leave WARP waiting for queued work during device destruction.
Reactor integration behavior is also covered by Reactor's recording and live surface tests. The
Composition Canvas sample covers the system bridge, and the Stacker Reactor sample covers the
lifted bridge.
Crates Windows Clang (`docs/crates/windows-clang.md`)
windows-clang
Generates RDL from C/C++ headers using libclang.
windows-clang is the header-facing stage of the Windows metadata pipeline:
headers -> windows-clang -> RDL -> windows-rdl -> WinMDIt extracts declarations and source annotations from C and C++ headers. Compile the resulting
RDL with windows-rdl, then use windows-bindgen to
generate Rust bindings.
Setup
The README shows the Cargo dependency. Install a compatible
libclang and set LIBCLANG_PATH to its directory if it is not discoverable on your system.
The crate loads libclang at runtime; it does not install it or the headers you want to process.
Supply compiler arguments for the language, target architecture, include directories, and
preprocessor definitions your headers require. Metadata references do not supply C/C++ headers
or compiler include paths.
Generate RDL
Configure the builder, then call write():
windows_clang::clang()
.input("Example.h")
.args(["-x", "c++", "--target=x86_64-pc-windows-msvc"])
.reference_default()
.namespace("Example")
.library("example.dll")
.output("Example.rdl")
.write()
.unwrap();At least one input, a namespace, and an output path are required. Paths are relative to the
generator's working directory.
| Builder method | Purpose |
|---|---|
input(path) / inputs(paths) |
Read header files as separate translation units. |
input_text(source) |
Add an in-memory translation unit. |
args(arguments) |
Pass compiler arguments to libclang. |
filter(suffix) |
Include declarations from included headers matching a path suffix. |
symbols(names) |
Restrict emitted free functions to the supplied names. |
reference(path) |
Resolve existing declarations from a WinMD file. |
reference_default() |
Use the bundled Windows metadata as references. |
namespace(name) |
Set the output RDL namespace. |
library(name) |
Set the DLL name attached to emitted functions. |
output(path) |
Set the output RDL file. |
Declarations from input headers are output roots. Included headers can supply dependencies without
having all their declarations emitted; use filter to include their declarations as roots too.
This is a header-path filter, not a function-name filter.
References let generated RDL use existing types without redefining them. Use reference_default
when your headers depend on Windows APIs and reference for your own metadata.
The builder reference
documents all methods.
Inspect extraction results
Use Input, extract, and EmitOptions directly when a generator needs to inspect declarations,
assign different DLLs to functions, or emit separate header partitions.
let input = windows_clang::Input::new(
"Example.h",
std::fs::read_to_string("Example.h").unwrap(),
);
let snapshot = windows_clang::extract(
[input],
&["-x", "c++", "--target=x86_64-pc-windows-msvc"],
)
.unwrap();The resulting Snapshot owns translation-unit-local facts and constants. facts, constants,unsupported, and dump expose the extraction result for diagnostics and validation.
Input::new(name, source) treats name as a root. The following methods control which included
headers contribute output roots:
| Method | Selects |
|---|---|
with_roots(paths) |
Individual header paths. |
with_root_dirs(paths) |
Headers under directory prefixes. |
with_root_suffixes(suffixes) |
Headers matching path suffixes. |
with_excluded_roots(paths) |
Headers excluded from output ownership. |
Configure emission
Use Snapshot::emit(namespace) for one namespace, emit_with_library to attach one DLL to all
functions, or emit_with_options for generator policy. emit_by_header_with_options returns a
map of defining-header names to RDL partitions.
EmitOptions controls:
| Option | Purpose |
|---|---|
namespace |
RDL namespace. |
library / libraries |
Default or per-function DLL mappings. |
references |
External types and enum members available to the projection. |
excluded_types |
Types excluded from root selection. |
excluded_functions |
Functions omitted from the local output. |
excluded_constants |
Constants omitted from the local output. |
functions |
Optional free-function allowlist. |
References are explicit TypeReference values classified as Type, Interface, or Enum.
Referenced enum member names let an overlay emit an enum only when it adds members to the base.MetadataReferences builds that map from WinMD files and records existing type, function, and
constant names for exclusion. apply_reference_exclusions excludes only types with an
unambiguous external reference, while apply_exclusions is available for overlays that must omit
every item from a known base.
Exclusions filter root declarations; they do not establish that a dependency has an external
definition. Supply references for external types; required local dependencies are still emitted.
The caller owns DLL selection, architecture merging, and RDL-to-WinMD compilation. Extraction
does not discover import libraries or combine target architectures automatically.
Known limits
- RDL cannot represent mixed raw pointer-chain mutability. If any level in a raw pointer run is
const, every level in that run is emitted as const. - Coverage is limited to declarations reachable from configured roots.
- Conflicting declarations can fail emission as ambiguous; extraction is not a general C++ binding
generator. - SAL projection supports only a subset of annotations and size relationships.
- Non-finite floating-point constants and interface-valued constants cannot be emitted.
- Header extraction cannot infer curated handle cleanup, last-error, or documentation policy.
- Stable Rust has no function-pointer ABI corresponding to every native calling convention.
Contributing
Run cargo test -p windows-clang and cargo test -p test_clang. Header fixtures and expected RDL
live in crates/tests/libs/clang; tests regenerate the expected output, and CI rejects output
changes that are not committed. After changing extraction or emission, run the affected tool-*
generators and review the output diffs.
Repository tooling provisions its pinned libclang with cargo run -q -p tool-clang -- path.
Set LIBCLANG_PATH to the reported directory before running tests.
Crates Windows Collections (`docs/crates/windows-collections.md`)
windows-collections
Stock WinRT collection types backed by Rust containers.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
When to use this crate
Use windows-collections when a WinRT API expects a collection interface and your data starts as a
Rust Vec or BTreeMap. The crate supplies stock implementations of the standard WinRT
interfaces, so application and component code do not need to implement their vtables.
This is an ABI adapter, not a replacement for Rust collections. Keep data as Vec or BTreeMap
while it remains inside Rust, and convert at the boundary where a WinRT collection is required.
Getting started
The crate README contains the dependency declaration and
minimal vector and map examples. The stock implementations require the default std feature. For
a first workflow:
- Identify the exact interface in the receiving API's signature.
- Build the values in a
VecorBTreeMap. - Convert the container with
InterfaceType::from. - Pass or return the projected interface and handle its methods as fallible operations.
use windows_collections::IVector;
fn numbers() -> IVector<i32> {
let values = IVector::<i32>::from(vec![1, 2, 3]);
values.Append(4).unwrap();
values
}In application code, propagate the Result returned by collection methods instead of unwrapping
when the operation can fail.
Choosing a collection interface
| Interface | Backing input | Choose it when the consumer needs |
|---|---|---|
IIterable<T> |
Vec<T::Default> |
Forward iteration only |
IVectorView<T> |
Vec<T::Default> |
Indexed, read-only access |
IVector<T> |
Vec<T::Default> |
Indexed mutation |
IObservableVector<T> |
Vec<T::Default> |
Mutation notifications |
IMapView<K, V> |
BTreeMap<K::Default, V::Default> |
Read-only lookup |
IMap<K, V> |
BTreeMap<K::Default, V::Default> |
Insert, remove, and lookup |
IObservableMap<K, V> |
BTreeMap<K::Default, V::Default> |
Map mutation notifications |
T::Default is the Rust storage form defined by the type's RuntimeType implementation. For
ordinary value types it is usually T. Projected nullable or interface types may use anOption<T> storage form, so let the compiler guide the concrete container type.
The mutable interfaces own their Rust containers behind synchronization. View conversions own
read-only values, and GetView on a mutable collection returns a snapshot rather than a live view.
Common tasks
Build and read a map
use std::collections::BTreeMap;
use windows_collections::IMapView;
use windows_strings::{h, HSTRING};
fn main() {
let values = BTreeMap::from([
(HSTRING::from("one"), 1),
(HSTRING::from("two"), 2),
]);
let values = IMapView::<HSTRING, i32>::from(values);
assert!(values.HasKey(h!("one")).unwrap());
assert_eq!(values.Lookup(h!("two")).unwrap(), 2);
}Missing keys and invalid vector indexes return the WinRT E_BOUNDS error. Check with HasKey orSize when absence is expected rather than using an error as normal control flow.
Iterate projected values
Generated IntoIterator implementations allow a borrowed or owned collection to be used in afor loop. Each item is still a projected value, and map iteration yieldsIKeyValuePair<K, V>:
# use windows_collections::IIterable;
let values = IIterable::<i32>::from(vec![1, 2, 3]);
for value in &values {
println!("{value}");
}Iteration uses GetMany in blocks instead of making one iterator ABI call for each item. Reading
each map pair's Key and Value remains a separate ABI operation.
Publish collection changes
Convert a container to IObservableVector or IObservableMap when consumers needVectorChanged or MapChanged. Register the projected event handler, retain its token, and pass
that token to the corresponding remove method when the subscription ends. Mutating through the
observable interface raises insertion, removal, replacement, or reset notifications.
Important choices and pitfalls
- Convert to the interface the API asks for. A mutable
IVector<T>is unnecessary when the API
acceptsIIterable<T>orIVectorView<T>. - Conversion consumes the Rust container. Clone it first only when Rust must retain an independent
copy. GetViewsnapshots the current contents. Later writes to the mutable source are not reflected in
that view.- Map iteration snapshots entries when
Firstis called. It does not hold a live traversal of the
backingBTreeMap. - Calls cross a WinRT ABI and return
Result; repeated fine-grained lookups can cost more than
processing the original Rust container before conversion.
Samples and next steps
The collections-collections sample converts aVec and a BTreeMap, mutates the projected interfaces, and iterates their values. Continue with
the
docs.rs API for the methods on the interface required by
your Windows API. windows-reference covers boxed WinRT values that can
also appear as collection elements.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-collections.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/collections.txt;
the collection adapters are hand-written. Used internally by the windows crate.
Iterating a collection (for x in &vector) yields through BufferedIterator
(src/buffered_iterator.rs), which fetches elements a block at a time via GetMany rather than oneIIterator::next ABI call per element. windows-bindgen generates the IntoIterator impls that
reference it. The block is sized to keep the buffer near 2 KB regardless of element size.
This batching applies to maps too: IMap/IMapView/IObservableMap implementIIterable>, so for pair in &map drives the same BufferedIterator (yieldingIKeyValuePair items) - there is no separate, slower map path. A map iteration still costs more
than a vector of scalars, but not because of the iterator: GetMany over IVector
bulk-copies the values inline, whereas over a map it returns a block of IKeyValuePair COM objects
(one AddRef each) and reading every pair.Value()/Key() remains a per-pair ABI crossing - theIMap ABI offers no bulk key/value read. Separately, the component-side stock map iterator
snapshots its entries once at First() so each step is O(1) rather than re-walking the tree,
keeping a full traversal linear.
Testing
Run cargo test -p windows-collections; see also the workspace test crates.
Crates Windows Composition (`docs/crates/windows-composition.md`)
windows-composition
Safe wrappers for the Windows retained-mode composition engine.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
- Reactor hosting samples
- Canvas guide
- Window guide
When to use it
Use Composition for a retained tree of lightweight visuals, brushes, shapes, and animations. It is
a good fit for animated backgrounds, overlays, game boards, transitions, and scenes where Windows
should retain and compose objects between updates.
Composition does not provide controls, text input, accessibility behavior, or layout panels. Usewindows-reactor for WinUI controls. Usewindows-canvas when custom drawing is easier as immediate-mode 2D commands.
Composition can host Canvas-drawn surfaces.
The basic idea
Composition is a retained graphics API. You create a tree of visual objects, change their
properties when application state changes, and let Windows draw and animate the result.
create visuals -> attach them to a tree -> update properties -> Windows composes the sceneThis differs from Canvas, where the application issues drawing commands for each frame. A
Composition visual stays in the tree until it is removed. Changing its offset, size, brush, or
animation is enough to change what appears on screen.
A Compositor creates every object in a scene. The most common visual types are:
| Type | Purpose |
|---|---|
ContainerVisual |
Groups child visuals without drawing content |
SpriteVisual |
Draws a rectangular brush and can contain children |
ShapeVisual |
Draws retained vector shapes |
Start with sprite visuals. They are enough for backgrounds, cards, overlays, game pieces, and many
animations.
Choose how to host the scene
The crate supports two Composition stacks:
| Feature | Use it when |
|---|---|
system (default) |
The app owns a windows-window window |
reactor |
The scene belongs inside a Reactor view |
The two stacks use different Windows APIs and their objects cannot be mixed. Cargo features are
mutually exclusive, so applications selecting Reactor must disable the default system feature:
windows-composition = { version = "0.100.0", default-features = false, features = ["reactor"] }Both stacks can create a graphics device and drawing surface for windows-canvas. Enable Canvas'scomposition feature and select its matching system or reactor feature.
The examples below begin with the default system stack because it shows the visual model with the
least framework code.
Your first visual tree
This complete program creates a window and fills it with a dark sprite:
use windows_composition::*;
use windows_window::*;
fn main() -> Result<()> {
let _queue = DispatcherQueueController::create_on_current_thread()?;
let compositor = Compositor::new()?;
let window = Window::new("Composition")
.size(800, 600)
.create()?;
let target = compositor.create_desktop_window_target(&window, false)?;
let root = compositor.create_sprite_visual();
root.set_relative_size_adjustment(Vector2::new(1.0, 1.0));
root.set_brush(&compositor.create_color_brush(CompositionColor::rgb(30, 30, 46)));
target.set_root(&root);
run();
Ok(())
}The dispatcher queue and target must stay alive while the scene is displayed. Declaring them inmain keeps their lifetimes clear.
The Compositor creates both the visual and its brush. Composition objects in one tree should come
from the same compositor.
Build a scene from nested visuals
Visuals form an ordered tree. Insert a child at the top to draw it in front of existing children,
or at the bottom to draw it behind them:
let card = compositor.create_sprite_visual();
card.set_size(240.0, 160.0);
card.set_offset(40.0, 40.0, 0.0);
card.set_brush(&compositor.create_color_brush(CompositionColor::rgb(0, 120, 215)));
let badge = compositor.create_sprite_visual();
badge.set_size(32.0, 32.0);
badge.set_offset(192.0, 16.0, 0.0);
badge.set_brush(&compositor.create_color_brush(CompositionColor::rgb(255, 255, 255)));
card.children().insert_at_top(&badge);
root.children().insert_at_top(&card);The badge's offset is relative to the card. Moving the card moves both visuals. Keep handles to
visuals that the application will change later; attached visuals remain in the scene even if a
local clone is dropped.
Use the child collection to change the tree:
root.children().remove(&card);
root.children().remove_all();Size and position visuals
set_size uses width and height. set_offset uses x, y, and z:
visual.set_size(200.0, 120.0);
visual.set_offset(24.0, 48.0, 0.0);Composition does not lay out a scene like WinUI. Either update exact sizes when the host changes or
make a child track its parent:
background.set_relative_size_adjustment(Vector2::new(1.0, 1.0));A relative size of (1.0, 1.0) fills the parent. Relative adjustments can be combined with an
exact size or offset, which is useful for margins:
panel.set_relative_size_adjustment(Vector2::new(1.0, 1.0));
panel.set_size(-32.0, -32.0);
panel.set_offset(16.0, 16.0, 0.0);Coordinates are device-independent pixels. The host is still responsible for reporting its own
size changes when exact dimensions are used.
Animate a property
Animations are retained objects too. Create an animation, add key frames, and start it on a visual:
use std::time::Duration;
let pulse = compositor.create_vector3_key_frame_animation();
pulse.insert_key_frame(0.0, Vector3::new(1.0, 1.0, 1.0));
pulse.insert_key_frame(0.5, Vector3::new(1.2, 1.2, 1.0));
pulse.insert_key_frame(1.0, Vector3::new(1.0, 1.0, 1.0));
pulse.set_duration(Duration::from_millis(800));
pulse.set_iterate_forever();
card.start_animation("Scale", &pulse);Windows advances the animation without an application frame loop. UseScalarKeyFrameAnimation for one-number properties such as "Opacity" andVector3KeyFrameAnimation for properties such as "Scale" and "Offset".
Property names are exact, case-sensitive Composition strings. Rust cannot catch a misspelling in"Scale".
Draw retained shapes
Use a ShapeVisual when the scene needs an ellipse or another vector shape rather than a
rectangular sprite:
let visual = compositor.create_shape_visual();
visual.set_size(200.0, 200.0);
let geometry = compositor.create_ellipse_geometry();
geometry.set_radius(Vector2::new(80.0, 80.0));
let circle = compositor.create_sprite_shape(&geometry);
circle.set_offset(Vector2::new(100.0, 100.0));
circle.set_fill_brush(&compositor.create_color_brush(CompositionColor::rgb(0, 120, 215)));
visual.shapes().append(&circle);
root.children().insert_at_top(&visual);Shapes remain editable after they are attached. Change the geometry, brush, or visual properties
instead of rebuilding the scene for every update.
Put Composition inside Reactor
With the reactor feature, a Reactor Grid provides the host. The flow is:
- Attach an
ElementRef<Grid>to the grid. - Observe the composition host from
use_effect. - On
CompositionHostEvent::Ready, adopt the supplied compositor withCompositor::from_host. - Build a visual tree and attach it with
request_set_child_visual. - Update its size on
ReadyandMetricsevents.
The important bridge calls look like this:
let compositor = Compositor::from_host(compositor)?;
let root = compositor.create_sprite_visual();
root.set_relative_size_adjustment(Vector2::new(1.0, 1.0));
root.set_brush(&compositor.create_color_brush(CompositionColor::rgb(30, 30, 46)));
let _ = host.request_set_child_visual(Some(root.host_visual()), |result| {
if let Err(error) = result {
eprintln!("failed to attach composition visual: {error:?}");
}
});The observation must live until the effect is cleaned up. Thehost sample contains the complete
component lifecycle; start there rather than copying only the bridge calls.
What to read next
| Sample | What it shows |
|---|---|
circles |
A larger shape scene |
minesweeper |
Input, layout, shapes, and animation |
Browse crates/samples/composition for focused hosting and
animation examples.
CompositionScopedBatch::on_completed returns an event revoker that must remain alive until the
tracked work finishes. In a Reactor component, have the callback send a message and update the
retained scene from Component::update.
Internal documentation
The remainder of this page describes how the crate is built and maintained. Applications do not
need it to use Composition.
Dependencies and feature model
The crate depends on windows-core, windows-numerics, windows-collections, and windows-time,
not the umbrella windows crate. Visual offsets and sizes reuse Vector2 and Vector3. Shape
collections use IVector, implicit animation collections use IMap, and animation timing usesTimeSpan.
The system feature also depends on windows-window and enables dispatcher queue, HWND target,
graphics-device, and drawing-surface support. The reactor feature selects lifted bindings and
exposes host conversions. Compile errors reject zero or two selected stacks.
Cargo unifies features across a build graph. Unified CI jobs that build Reactor exclude
system-stack consumers, then check those consumers in a separate step. Add new consumers to the
matching CI groups. tool-yml generates the stack-specific MSRV and feature checks.
Wrapper model
Each safe type is a newtype over one owned COM interface. Concrete visual types dereference through
their base hierarchy. Brush, Shape, and Animation are sealed traits that keep private
generated binding types out of public signatures.
Constructors and immediate recoverable operations return windows_core::Result. Retained-object
property and factory conveniences fail fast on COM errors. CompositionColor wraps the flatWindows.UI.Color ABI value.
| Module | Contents |
|---|---|
compositor.rs |
Factories for graph objects and stack-specific hosts |
visual.rs |
Visual hierarchy, properties, and child collections |
brush.rs |
Brush hierarchy and implementations |
shape.rs |
Shape visuals, geometry, shapes, and collections |
animation.rs |
Key frames, easing, groups, and implicit animations |
batch.rs |
Scoped batches and batch kinds |
surface.rs |
System and lifted graphics devices and drawing surfaces |
stack.rs, target.rs |
System dispatcher queue and HWND hosting |
color.rs |
Public color value |
Code generation
Composition uses tool-composition because it needs system and non-default lifted metadata. The
tool first uses windows-rdl to generate the lifted native interop metadata from interop.rdl,
then runs windows_bindgen twice with --flat --minimal --dead-code.
- System bindings read the repository's Windows and Win32 metadata and include
Windows.UI.Composition, dispatcher queue, desktop interop, and surface interop APIs. - Lifted bindings read the pinned Microsoft UI and Graphics metadata used by Reactor, the
repository's Windows and Win32 metadata, and the generated interop metadata. They includeMicrosoft.UI.Compositionwith shared foundation, color, DirectX, and native interop types.
crates/tools/composition/src/composition.txt is the shared filter. System-only regions are omitted
from lifted generation. Composition, DirectX, and native interop entries are rewritten for the
lifted stack. lib.rs selects bindings.rs or bindings_lifted.rs.
Generated binding files are committed and must not be edited by hand. After changing the filter,
metadata inputs, or tool, run:
cargo run -p tool-composition --quiet
cargo check -p windows-composition --quietRun cargo run -p tool-yml after changing feature-matrix generation.
Reactor bridge
Reactor reports CompositionHostEvent through a typed ElementRef<Grid>. A ready event contains
an application-safe compositor capability and layout metrics. Compositor::from_host adopts that
capability. Visual::host_visual exports only an application-owned lifted visual for attachment.
Both crates generate lifted bindings from the same pinned Microsoft UI metadata. Host attachment
and observation commands exist in Reactor's native and recording runtimes. Window and node
identities reject late events and completions.
Reactor also consumes lifted key-frame, group, easing, and implicit-animation wrappers for element
lifecycle transitions.
Canvas bridge
Each stack exposes matching compositor, graphics-device, and drawing-surface interop interfaces. A
Canvas device becomes a CompositionGraphicsDevice, which creates a premultiplied BGRA drawing
surface. A composition surface brush paints that surface onto a visual.
windows-canvas implements the drawing half behind its composition feature. It borrows the
Direct2D context returned by begin_draw, applies the backing-atlas offset, and pairs the operation
with end_draw.
Testing
test_composition includes pure value tests and live headless tests. The live tests create a
dispatcher queue and real compositor, then exercise visual, brush, shape, collection, and animation
wrappers without a window. Getter checks also verify that wrapper methods route to the intended
versioned COM interface.
Run:
cargo test -p test_compositionWindow hosting is covered by the standalone sample. Reactor host behavior is covered by Reactor's
recording tests and live self-test fixtures.
Crates Windows Core (`docs/crates/windows-core.md`)
windows-core
Core COM and WinRT type support shared by the windows-* crates.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
When to use this crate
Use windows-core directly when you need the common types behind generated Windows bindings,
author a COM or WinRT interface in Rust, or work with COM identity, apartments, factories, or
references. It provides IUnknown, IInspectable, Interface, GUID, RuntimeType, and the
support used by generated projections.
Most code should start with a focused windows-* crate instead. Those crates already depend on and
re-export the core types they need. Binary applications may use the broadwindows projection. Add a direct windows-core dependency when your own public API
names these types or when no higher-level crate
owns the operation.
Getting started
The crate README has the dependency declaration and a minimal
example using strings and results. For a first COM or WinRT workflow:
- Select or generate bindings for the Windows API you want to call.
- Initialize the calling thread's COM apartment when the API requires it.
- Keep projected interface values as owned Rust values and propagate
windows_core::Result. - Use
Interface::castwhen you need another interface implemented by the same object.
For apartment-agnostic command-line code, init_mta initializes an uninitialized calling thread as
MTA and keeps the process MTA alive:
use windows_core::Result;
fn main() -> Result<()> {
windows_core::init_mta()?;
// Create and use projected COM or WinRT objects here.
Ok(())
}If a UI framework or host initializes COM for you, follow its apartment model instead. init_mta
does not change a thread that is already initialized in another apartment.
For a standalone UI thread, init_sta initializes COM as a single-threaded apartment. Keep the
returned value alive until after the apartment's COM interface values are dropped:
use windows_core::Result;
fn main() -> Result<()> {
let _apartment = windows_core::init_sta()?;
// Create and use apartment-bound COM objects here.
Ok(())
}Core API model
| API | Role |
|---|---|
IUnknown |
Owning pointer to the base COM interface |
IInspectable |
Base interface for WinRT objects |
Interface |
Interface identity, vtable access, casts, and raw-pointer interop |
GUID |
Interface IDs and other Windows GUID values |
create_instance |
Activates an in-process COM class as a requested interface |
init_mta, init_sta |
Initializes COM using the required apartment model |
StaApartment |
Keeps an STA initialized on its owning thread |
Result<T>, Error, HRESULT |
Re-exported Windows error model |
HSTRING, PCWSTR, PCSTR |
Re-exported Windows string model |
AgileReference<T> |
Reference that resolves an apartment-valid proxy |
Weak<T> |
Non-owning reference that can be upgraded while the object is alive |
EventRevoker |
Event registration that unregisters when dropped |
#[interface], #[implement] |
Declare an interface and implement it with a Rust type |
Projected interface values are reference-counted owners. Cloning one performs the corresponding
COM reference-count operation, dropping it releases the reference, and cast performsQueryInterface. Prefer these operations over manual AddRef, Release, or pointer casts.
create_instance::<T>(&class_id) wraps the common non-aggregatedCoCreateInstance(..., CLSCTX_INPROC_SERVER, ...) call. The caller remains responsible for COM
apartment initialization and for choosing a class that supports the calling apartment.
Common tasks
Moving between interfaces
Use cast to request another interface from the same COM identity:
use windows_core::{Interface, IInspectable, IUnknown, Result};
fn as_inspectable(value: &IUnknown) -> Result<IInspectable> {
value.cast()
}A failed query is returned as an Error. A Rust type conversion is not a replacement forQueryInterface unless the generated API provides that conversion.
Sharing an apartment-bound object
Do not assume that every interface can be sent to another apartment. Create anAgileReference<T>, move or clone that reference, and call resolve in the apartment where the
object will be used:
use windows_core::{AgileReference, Interface, Result};
fn make_agile<T: Interface>(value: &T) -> Result<AgileReference<T>> {
AgileReference::new(value)
}Weak<T> serves a different purpose: it avoids keeping an object alive and returns None fromupgrade after the object has been destroyed.
Declaring and implementing an interface
The default proc-macros feature re-exports #[interface] and #[implement]:
use windows_core::*;
#[interface("7e75ffe0-2f8c-4040-953e-b1f83a48f77b")]
unsafe trait IValue: IUnknown {
unsafe fn value(&self) -> i32;
}
#[implement(IValue)]
struct Value {
value: i32,
}
impl IValue_Impl for Value_Impl {
unsafe fn value(&self) -> i32 {
self.value
}
}
fn main() -> Result<()> {
let object: IValue = Value { value: 42 }.into();
assert_eq!(unsafe { object.value() }, 42);
let unknown: IUnknown = object.cast()?;
let object: IValue = unknown.cast()?;
assert_eq!(unsafe { object.value() }, 42);
Ok(())
}#[interface] generates the vtable, caller wrappers, and implementation trait. #[implement]
generates the wrapper that owns the vtables and reference count. The implementation methods remain
responsible for the interface contract and any safety requirements in the ABI.
Important choices and pitfalls
- The default features are
stdandproc-macros. Disableproc-macroswhen avoiding thesyn,quote, andproc-macro2build dependencies matters; the narrowerinterface_decl!andimplement_decl!macros remain available. - Raw interface pointers do not carry an owning lifetime. Use
from_raw,from_raw_borrowed,into_raw, and relatedInterfaceoperations only when the ownership contract is known. - COM apartment initialization is per thread. Initializing one thread does not initialize worker
threads. EventRevokerunregisters on drop. Keep it alive for as long as the handler should run; useinto_tokenonly when another owner will remove the registration.- The
impmodule supports generated code and crate internals. Application code should prefer the
public projected types and traits.
Samples and next steps
There is no standalone windows-core sample group. Thewindows samples show Result, init_mta, strings, and projected
interfaces in complete API calls. The robot sample shows a Rust
component implementing generated COM and WinRT interfaces for multiple clients.
Continue with windows-result for error propagation andwindows-strings for ABI string choices.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-core.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/core.txt. The
hand-written modules (agile_reference, com_object, compose, event) provide the COM runtime
support. The #[implement]/#[interface] proc macros are re-exported from thewindows-implement/windows-interface crates
behind the proc-macros feature, and the implement_macro/interface_macro modules supply theimplement_decl!/interface_decl! declarative equivalents used when that feature is off.
Testing
Run cargo test -p windows-core; see also the workspace test crates.
Crates Windows Default (`docs/crates/windows-default.md`)
windows-default
The default Windows metadata used by the build-time crates.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
windows-default embeds the canonical Windows Runtime and Windows API metadata as byte slices.
Build tools use WINRT and WIN32 when they need the standard metadata without locating separate.winmd files.
Applications should use a focused crate, windows-bindgen, or, for binaries that want a broad
pre-generated surface, windows or windows-sys. Most code
generators should use input_default or reference_default on the builder they already use. Usewindows-default directly only when implementing a metadata tool that accepts in-memory winmd
bytes.
The crate exposes two static byte slices:
| Static | Embedded file | Contents |
|---|---|---|
WINRT |
Windows.winmd |
Windows Runtime contracts |
WIN32 |
Windows.Win32.winmd |
Windows SDK and WDK APIs |
Both files are included in the published crate and embedded with include_bytes!. The crate does
not extract files or perform I/O.
First workflow: add standard metadata to a custom index
- Construct
windows_metadata::reader::Filevalues fromWINRTandWIN32. - Add component-specific winmd files.
- Build one
windows_metadata::reader::Indexover the combined files. - Query or transform the index without locating an SDK installation.
tool-reactor-metadata uses this pattern when its metadata resolver combines WinUI winmds with the
standard definitions. If you are compiling RDL or generating Rust, use the neighboring builder
methods instead; they already perform the byte-to-file conversion.
Integration
The build crates depend on windows-default and expose the metadata through their own builders.
Callers therefore do not need a separate dependency or a path into the Windows SDK.
| Crate | Default behavior |
|---|---|
windows-bindgen |
Implicit with no input; explicit with input_default. |
windows-rdl |
reference_default or writer input_default. |
windows-clang |
reference_default and WinRT-only resolution_default. |
windows-metadata |
Construct reader::File values from the slices. |
The windows-bindgen textual adapter accepts --in default. The bindgen, RDL, and Clang builders
use explicit default methods instead; their path-style input methods treat "default" as an
ordinary path. Byte-input APIs remain available for custom metadata that is already in memory.
Programs that link one of these build crates include both metadata payloads in the binary. These
crates are intended for build tools rather than runtime dependencies.
Pitfalls
WIN32is one flatWindows.Win32metadata set. Publishedwindowsandwindows-sysfeature
namespaces are created later bytool-package.- Adding any explicit bindgen input disables the implicit default input. Call
input_defaultwhen
the custom metadata references standard types. reference_defaultandresolution_defaultare not synonyms inwindows-clang. The former can
suppress already-defined declarations; the latter only classifies WinRT ABI projections.- Linking this crate embeds both payloads even if the tool reads only one static.
- The bytes are build inputs. They do not provide Windows DLLs or make an API available on the
running operating system.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-default.
How it's built
The crate has no dependencies. src/lib.rs exposes each committed .winmd throughinclude_bytes!.
| File | Generator | Reviewable source |
|---|---|---|
Windows.winmd |
cargo run -p tool-winrt |
metadata/winrt |
Windows.Win32.winmd |
cargo run -p tool-win32 |
metadata/win32 and metadata/wdk |
tool-winrt merges the Windows SDK contract metadata, writes canonical RDL, and compiles that RDL
back to Windows.winmd. tool-win32 scrapes the Windows SDK and WDK headers for X64, Arm64, and
X86, merges the architecture-specific RDL, and writes Windows.Win32.winmd.
For missing SDK coverage, add the declaring header to tool-win32's header list and check that its
exports have an import library in the existing list. Regenerate the metadata with tool-win32,
then run tool-package and tool-features to update the published bindings and feature index.
Do not hand-edit the generated RDL or metadata.
See Dependencies for the package versions
and provenance.
Packaging
The repository .gitignore normally excludes .winmd files, with an exception for this crate's
two committed payloads. Cargo packages tracked files by default, so Cargo.toml does not need a
separate include list. cargo package -p windows-default must contain both .winmd files.
Testing
The metadata is exercised through its consumers:
test_bindgen,test_rdl, and thewindows-clangintegration tests cover default and byte
inputs.tool-winrt,tool-win32, andtool-roundtripverify deterministic metadata generation.- The
tool-ymlgenerated workflows build and documentwindows-defaultlike the other library
crates.
After changing the payloads or their generators, run the owning generator and confirm that the
other generators remain output-neutral.
Crates Windows Future (`docs/crates/windows-future.md`)
windows-future
Stock async types for Windows APIs.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
windows-future provides IAsyncOperation and the related WinRT async interfaces, with
conveniences for producing and consuming them from Rust.
When to use
Use this crate when an API returns one of the four WinRT async interfaces, or when your own WinRT
API must return one:
IAsyncActionIAsyncOperation<T>IAsyncActionWithProgressIAsyncOperationWithProgress<T, P>
Use an async runtime's native task type for Rust-only work that does not cross a WinRT boundary.windows-future adapts WinRT operations to Rust futures; it is not an async executor.
Getting started
The crate README has the dependency declaration and a minimalready/spawn example. A typical first workflow is to receive an IAsyncOperation<T> from a
Windows API, choose how the surrounding code should wait for it, and propagate itswindows_core::Result<T>:
async fn consume(operation: windows_future::IAsyncOperation<i32>) -> windows_core::Result<i32> {
let value = operation.await?;
Ok(value + 1)
}The operation begins according to the WinRT API's rules; converting it into a future does not
create or schedule the operation.
Consuming an operation
Choose one completion style and let it own the operation's completion handler:
| Style | Use it when |
|---|---|
.await |
The caller is already async and the default std feature is enabled. |
join() |
Synchronous code may block the current thread until completion. |
when(callback) |
Completion should invoke a Send + 'static callback without blocking. |
All three paths return or receive a Result, including failures reported by the WinRT operation.
Avoid join() on a UI thread or another thread that must continue pumping work.
Producing an operation
ready wraps a result that is already available. spawn runs a Send + 'static closure on the
Windows thread pool and exposes its result as a WinRT async object. Pick IAsyncAction for ()
and IAsyncOperation<T> for a value. The progress variants provide the corresponding interface
shape, but closures passed to spawn do not report intermediate progress.
The std feature is enabled by default and supplies ready, spawn, and the IntoFuture
implementation used by .await. Without it, the interface types and the join/when consumption
helpers remain available.
Platform constraints and pitfalls
- The crate targets Windows and WinRT async interfaces.
- A WinRT completion handler can be assigned only once. These helpers may assign that handler while
an operation is running, so do not combine them or a directSetCompletedcall on one operation. spawnis for blocking or synchronous work that is safe on a pool thread. It does not make
thread-affine UI or COM work safe to move off its owning thread.readyandspawnexposeCancelandClosethroughIAsyncInfo, but these constructors do
not interrupt the supplied closure.
Sample and next steps
The spawn sample contrasts an immediately ready operation
with work submitted to the Windows thread pool. Next, inspect the API that produces your operation
to determine its result type, error behavior, cancellation support, and any thread affinity.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-future.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/future.txt andfuture_impl.txt; the Future bridge is hand-written.
Testing
Run cargo test -p windows-future; see also the workspace test crates.
Crates Windows Implement (`docs/crates/windows-implement.md`)
windows-implement
The procedural macro that exposes a Rust type through COM or WinRT interfaces.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
windows-implement provides #[implement]. The macro builds the vtables, identity object,
reference count, QueryInterface routing, and conversions needed to expose a Rust struct through
one or more generated interfaces.
Intended audience
Use this macro when Rust is the implementation side of COM or WinRT: a component, callback,
activation factory, shell extension, test object, or another object passed to an API as an
interface. Calling an existing interface does not require #[implement].
Application and component code normally imports windows_core::implement. The separatewindows-implement dependency is an implementation detail of windows-core's defaultproc-macros feature. Its generated code also depends on windows-core, so direct consumption
offers no standalone runtime.
Interfaces usually come from a focused crate or customwindows-bindgen output. A binary application may instead use the broadwindows projection. Pair this macro with#[interface] only for a manually owned or missing interface definition.
See windows-core for a compact end-to-end example.
Implementation model
For #[implement(IValue)] struct Value, the interface declaration or generated bindings provide anIValue_Impl trait. The macro creates a Value_Impl wrapper. Implement the generated trait for
that wrapper, then convert the original Value into an interface type.
For an inherited interface, implement the *_Impl traits required by the interface and its parent
chain. List each independently exposed interface in #[implement(...)]; do not list a base merely
to compensate for a missing parent relationship in the interface definition.
The resulting interface value owns a COM reference. Cloning an interface increments the reference
count, dropping it decrements the count, and cast uses QueryInterface. The original Rust value
is dropped when the final COM reference is released.
Implementation methods use the projected signatures generated for the interface. Returnwindows_core::Result<T> when offered by that trait, and return a precise HRESULT when the trait
uses raw status values. The generated thunk translates Result errors at the ABI boundary.
Threading and agility
Implementations are agile by default. The macro exposes IAgileObject and an agile marshaler
unless the attribute includes Agile = false. This is a COM promise, not a convenience flag.
Before accepting the default, verify that:
- method calls may arrive from the apartments and threads allowed by the interface contract;
- shared mutable state is synchronized correctly;
- borrowed thread-affine resources never escape their owning apartment;
- destruction is valid on the thread that releases the final reference.
Use Agile = false for an apartment-affine object. Add an interface such as IAgileObject
explicitly only when the object implements that contract itself. For WinRT implementations,TrustLevel = Partial or TrustLevel = Full controls the value reported byIInspectable::GetTrustLevel; omit it unless the component's contract requires a nondefault trust
level.
Errors, panics, and pointers
Treat every generated implementation method as an FFI boundary:
- Validate raw pointers before dereferencing them when the ABI permits validation.
- Initialize required out parameters on every success path and on failure paths required by the
interface contract. - Convert expected failures into
Error,Result, or the documented HRESULT. - Do not let a panic cross the generated external function. Remove panic paths from boundary code
or catch them at a layer that can map them to a defined failure. - Keep interface references alive for as long as stored raw pointers derived from them are used.
Error::new or Error::from_hresult preserves an HRESULT for projected Result methods. Use the
error code specified by the interface contract; an arbitrary failure code makes native callers
hard to diagnose.
Reference-counted interface fields can form cycles. COM reference counting does not collect them.
Use weak references where the interface model supports them or define an explicit teardown path.
Practical component workflow
- Generate or declare the interfaces and inspect their
*_Impltraits. - Design a Rust state type with explicit synchronization and apartment assumptions.
- Add
#[implement]with only the interfaces the object must expose. - Implement every required
*_Impltrait on the generated wrapper. - Convert the value to the narrowest interface returned to the caller.
- Test
castfor each supported and unsupported interface, not just direct method calls. - Test failure HRESULTs, out-parameter initialization, final release, and any cross-thread use
promised by agility.
For static factories and exported components, the repository's robot component sample showsStaticComObject, activation factory output, OutRef, and generated implementation traits. The
implementation test crates contain smaller examples for identity, inheritance, generic WinRT
interfaces, agility, and error propagation.
Pitfalls
- Implementing the Rust trait does not make an incorrect interface declaration ABI-safe.
- Interior mutability without synchronization can violate the default agile contract.
- Returning an interface to native code transfers a reference-counted ownership interest; avoid
temporary raw pointers that outlive their owner. castsuccess is governed by the interface list and inheritance graph generated intoQueryInterface.- Generic constraints from the annotated struct are carried into the generated wrapper. Keep them
compatible with every generated*_Impltrait.
When procedural macros are disabled, windows-core::implement_decl! covers the common always-agile
case with a more explicit and narrower declarative syntax. Use its module documentation as the
grammar reference.
Internal documentation
windows-implement is a syn/quote procedural macro crate. src/lib.rs parses the annotated
struct, interface list, generics, Agile, and TrustLevel options. src/gen.rs emits the wrapper,
vtable chains, identity and reference-counting implementation, interface conversions, andQueryInterface routing.
The crate depends on the *_Impl traits and vtable constructors emitted bywindows-interface or windows-bindgen. Generated paths target ::windows_core; the proc macro
crate is separated only to satisfy Rust's procedural macro packaging model.
Run cargo test -p windows-implement; the test_implement and test_implement_core crates cover
the runtime contract more broadly.
Crates Windows Interface (`docs/crates/windows-interface.md`)
windows-interface
The procedural macro that turns a Rust trait declaration into a COM interface.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
windows-interface provides the #[interface] attribute used to declare a COM interface that can
be called or implemented from Rust.
Intended audience
Prefer interfaces from a focused crate or generated withwindows-bindgen. Binary applications may also use interfaces from the broadwindows projection. Declare an interface manually only when the ABI is owned by the
project, the interface is missing from metadata, or a focused interoperability test needs an
independent declaration.
The macro is normally consumed as windows_core::interface, not by depending on this crate
directly. windows-core re-exports it through its default proc-macros feature and supplies every
runtime type referenced by the generated code. The separate crate exists because procedural macros
must be compiled as a proc-macro crate.
See windows-core for the paired declaration and implementation example. Use
the lower-level interface_decl! macro from windows-core only when the procedural macro feature
is disabled and its narrower grammar is sufficient.
Designing a declaration
An interface declaration records an ABI, not an ordinary Rust trait design. Verify these items
before writing it:
- Use the interface's exact IID. Reusing or mistyping an IID can make
QueryInterfacereturn an
object with an incompatible vtable. - Name the direct base interface after
:. UseIUnknownfor a base COM interface. - Preserve method order, including every inherited method represented by the parent.
- Match parameter types, pointer constness, return types, and Windows integer widths exactly.
- Mark the trait
unsafe; callers and implementors must uphold the ABI contract.
The accepted trait form is intentionally restricted. Methods take &self; async methods,
generics, explicit ABI declarations, variadics, default bodies, and non-documentation attributes
are rejected. One parent path describes the vtable inheritance chain.
For COM methods that return only an HRESULT status, Result<()> provides a projected caller and
implementor shape while the vtable entry returns HRESULT. An explicit HRESULT return preserves
the raw status. Do not replace an ABI out parameter with Result<T> by analogy: declare the
parameter shape expected by the macro and ABI. Ref<T> and OutRef<T> are supported for interface
input and output parameters and project to the corresponding Param and OutParam caller forms.
What the macro generates
For an interface named IValue, #[interface] generates:
- the transparent
IValueinterface wrapper; - an
Interfaceimplementation containing the IID and vtable type; Derefto the declared parent interface;- caller methods that dispatch through the vtable;
- the
IValue_Vtbllayout; - the
IValue_Impltrait used by#[implement]; - conversions and
QueryInterfacesupport needed bywindows-core.
Caller methods remain unsafe because the declaration cannot prove pointer validity or
API-specific invariants. The wrapper handles COM identity and reference counting throughwindows-core; it does not validate a hand-written ABI.
Practical workflow and pitfalls
Keep a manual interface declaration next to a citation or checked-in definition of its ABI. Add a
test that exercises both a known implementation and QueryInterface through the expected IID when
possible.
Common failures are severe:
- A wrong method order or signature dispatches through the wrong machine-level contract.
- A wrong parent produces an invalid inherited vtable layout.
- A correct IID paired with an incorrect layout can corrupt memory.
- Rust references in an ABI are valid only when their representation and lifetime are part of the
agreed Rust-to-Rust contract. Public COM ABIs normally use Windows ABI types and pointers. Result<()>translates an error into HRESULT at the boundary; panics are not COM errors and must
not unwind across an external call.
Prefer generating the declaration from .winmd with windows-bindgen when metadata can describe
the interface. That keeps caller and implementation projections aligned and avoids maintaining a
vtable by hand.
Internal documentation
windows-interface is a syn/quote procedural macro crate. src/lib.rs parses the restricted
trait and GUID. src/generation.rs emits the interface wrapper, vtable, implementation trait,
inheritance traversal, and Ref<T>/OutRef<T> parameter adapters. src/guid.rs parses the IID.
The generated paths are rooted at ::windows_core, which is why direct use still requires a
compatible windows-core dependency. The implementation macro consumes the generated *_Impl
traits and vtable constructors; changes to either macro must preserve that contract.
Run cargo test -p windows-interface; the test_interface and implementation test crates cover
cross-crate behavior.
Crates Windows Link (`docs/crates/windows-link.md`)
windows-link
A macro for declaring Windows DLL imports without an import library.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
windows-link provides the link! macro. It emits a raw external function declaration using
Rust's raw-dylib linking mode and a function-pointer type alias with the same name.
Intended audience
This crate is for binding generators and authors of narrow low-level bindings. The generatedwindows and windows-sys crates already declare and link the Windows APIs they expose, so
application code should not redeclare those functions.
Use windows-link directly when:
- an exported Windows function is absent from the available metadata;
- a project owns a small reviewed raw binding;
- generated
--sysbindings uselink!as their runtime link mechanism; - the function-pointer type emitted beside the declaration is useful to an abstraction.
Use windows-bindgen when several metadata-backed declarations are needed.
A binary application may use windows or windows-sys when the
published umbrella crates already contain the API. Reusable libraries should prefer focused crates
and narrow generated bindings.
Unlike #[interface] and #[implement], link! is not a procedural macro normally re-exported for
application use. Rich generated bindings invoke it through windows-core; raw generated bindings
depend on windows-link directly. Most users therefore encounter this crate as binding
infrastructure rather than as an application dependency.
Declaration contract
The README shows the macro grammar and its function-pointer alias. Every token in a declaration is
part of the ABI contract:
- The library literal must name the DLL that exports the symbol on supported Windows versions.
- The ABI literal must match the export, commonly
"system"for Windows APIs. - The optional link-name literal maps the Rust identifier to a differently named export.
- Parameter and return types must match the export exactly, including pointer constness and integer
width. - The declaration is unsafe even when a particular function happens to accept only integers.
On x86 Windows, the macro requests undecorated import names. On other Windows architectures it
uses a verbatim raw-dylib import. Do not add architecture-specific spelling to compensate without
checking the actual exported symbol and Rust's ABI behavior.
The type alias has the same identifier as the function because Rust has separate value and type
namespaces. This permits code such as let callback: GetTickCount = GetTickCount;. The alias is anunsafe extern function pointer and carries the same caller obligations as the direct declaration.
Practical workflow
Before adding a declaration:
- Search the
windowsandwindows-sysAPI reference for the symbol. - Confirm the DLL, export name, calling convention, and exact signature from an authoritative
Windows definition. - Decide which Windows versions and architectures the surrounding crate supports.
- Put the declaration in a private FFI module.
- Add a safe wrapper only if its preconditions, ownership, result convention, and cleanup can be
expressed correctly. - Test on each supported architecture when decoration, variadics, or unusual ABI types are
involved.
link! only arranges static import resolution. It is not delayed loading and does not make an
optional export safe to call on older systems. For optional APIs, resolve the symbol dynamically
and keep the module loaded for at least as long as the function pointer can be called.
Pitfalls
- A successful link does not prove the Rust signature is ABI-compatible.
- DLL forwarding and API-set names are Windows implementation details; use the library contract
documented for the API. - The macro does not generate feature gates. Add the surrounding
cfgand Cargo feature policy in
the binding crate. - A declaration produced on a non-Windows target does not make the Windows export available there.
- Variadic and uncommon calling conventions have Rust and architecture restrictions. Prefer
metadata-backedwindows-sysoutput when it supports the declaration. - Do not duplicate a symbol declaration with a different signature in the same program.
Internal documentation
The crate is hand-written and no_std. src/lib.rs contains the three link! expansions: x86
Windows, other Windows architectures, and non-Windows parsing support. The Windows expansions usekind = "raw-dylib" and +verbatim; x86 also sets import_name_type = "undecorated".
windows-bindgen emits this macro for sys-style bindings unless --extern is selected.windows-core re-exports the macro for rich and minimal generated bindings, while windows-sys
calls it through its direct dependency.
Run cargo test -p windows-link; generated binding tests exercise its wider use.
Crates Windows Metadata (`docs/crates/windows-metadata.md`)
windows-metadata
A low-level reader and writer for the ECMA-335 metadata format.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
windows-metadata reads and writes the ECMA-335 metadata format used by .NET, WinRT, and the Win32
metadata. It is the foundation windows-bindgen builds on. Thereader::Index type loads one or more .winmd files and lets you query namespaces, type
definitions, and their members.
Applications should start with a focused crate or windows-bindgen; binary applications may usewindows or windows-sys when they want a broad pre-generated
surface. Use windows-metadata directly when writing a metadata inspector, generator, merger, or
namespace transformation. The API exposes metadata facts; it does not project them into a safe Rust
API.
Start with the crate README for dependency setup and a
focused type query.
First workflow: inspect an API definition
- Load a file with
reader::Index::read, or constructreader::Filevalues and pass them toIndex::new. - Locate a type with
getwhen duplicates are possible orexpectwhen exactly one definition is
an invariant. - Inspect
category,extends, fields, methods, signatures, attributes, and nested types. - Keep rows borrowed from the index; use
leakorread_staticonly when a tool requires
process-lifetime rows.
For methods, match ECMA-335 parameter rows withMethodDef::params_by_sequence(signature.types.len()). Sequence 0 belongs to the return value;
nonzero sequences are one-based signature positions. params() instead returns physical table
order and is appropriate for lossless copying, not semantic parameter association.
tool-reactor-metadata provides a larger example. Its metadata resolver loads WinUI winmd files,
adds windows-default byte inputs, indexes classes and interfaces, then uses method signatures and
attributes to drive generated UI code. tool-features uses Index::iter_items to enumerate
types, free functions, and constants.
Input and output model
reader::File owns one winmd byte stream. reader::Index combines files into searchable
namespace, type, nested-type, and expanded Win32 Apis indexes. A zero architecture selector keeps
all rows; Index::new_for_architecture selects neutral rows plus one of X86 (1), X64 (2), or Arm64
(4).
The writer API is lower-level. writer::File builds ECMA-335 rows and into_stream returns the
finished bytes. Prefer windows-rdl when a reviewable source format is useful;
use the writer directly when a tool is copying or synthesizing table rows.
Common tool tasks
| Task | API |
|---|---|
| Read one winmd | reader::File::read or reader::Index::read |
| Read bytes already in memory | reader::File::new |
| Query several files together | reader::Index::new |
| Select one architecture | reader::Index::new_for_architecture |
| Merge ordinary winmds | merge().input(...).output(...).merge() |
| Merge per-architecture winmds | merge().arch_input(path, bits) |
| Union compatible duplicate enums | Merger::union_enums |
| Remap flat namespaces | remap().source(...).routes(...).fallback(...) |
| Author metadata tables | writer::File |
The merger accepts files or directories. Architecture merge retains one neutral definition only
when the same shape is present on every merged architecture; divergent shapes are tagged and kept.
The remapper rewrites definitions and references together and splits Win32 Apis members across
their routed target namespaces.
Integration and pitfalls
windows-defaultsupplies the standard WinRT and Win32 files as byte
slices. Constructreader::Filevalues from those bytes when a custom tool needs them.windows-rdlprovides a higher-level text authoring and decompilation path.windows-bindgenapplies Rust projection policy to these raw metadata
facts.- A metadata parameter marked neither In nor Out is
ParamDirection::Unspecified. Do not infer
input direction in the metadata layer. - Optional, reserved, retval, and buffer-count annotations are independent facts. Consumers decide
how those facts affect their public API. - Duplicate type definitions can be valid in an unfiltered multi-architecture index. Do not call
expectunless uniqueness is guaranteed. - Preserve physical row order when copying malformed or sparse metadata; use semantic helpers when
interpreting it.
The focused fixtures in crates/tests/libs/rdl and crates/tests/libs/bindgen show metadata
shapes. The main tool consumers are tool-winrt, tool-win32, tool-package, tool-features,
and tool-reactor-metadata.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-metadata.
How it's built
Consumed by windows-bindgen. src/bindings.rs is generated by tool-bindings fromcrates/tools/bindings/src/metadata.txt; the ECMA-335 tables and readers are hand-written.
Multi-arch merge
windows-metadata's merge module coalesces the per-architecture winmds that tool-win32 scrapes
(x64, arm64, x86) into a single winmd. A type identical across every arch is emitted once as
arch-neutral; a type that diverges is split into per-arch copies, each tagged with aSupportedArchitectureAttribute (spelled #[arch(X86|X64|Arm64)] in RDL).
reader::Index::new_for_architecture selects one of those copies while retaining neutral rows.
Its bit values match the metadata attribute: 1 for X86, 2 for X64, and 4 for Arm64. PlainIndex::new keeps every copy for tools that need to inspect or merge the complete metadata.
The collapse-or-split decision is structural, driven by type_sig() - a hash of everything that can
legitimately differ between architectures:
- fields - name, type, and constant value (so an enum whose members hold different per-arch
values splits instead of silently dropping the divergent values); - method signatures - Win32 callbacks and WinRT delegates have no fields and diverge only in
theirInvokesignature, so a fields-only signature would wrongly collapse arch-divergent
callbacks into one untagged copy; - layout -
#pragma packandClassLayout; AlignmentAttribute-__declspec(align(N))raised alignment is encoded only by that
attribute (ClassLayoutcan only lower alignment), so a type that differs solely in forced
over-alignment must fold the attribute into the signature or the divergent copy is lost;- flags - the type's attribute flags.
Types present on only a subset of arches still go through the same structural split, so a type
that is present on x64+arm64 (but not x86) and diverges between those two is split per arch rather
than collapsing to whichever copy happened to be first. A group is emitted arch-neutral only when
its signature spans every arch in the run.
Unmanaged callbacks have one narrow semantic reconciliation before that split. If at least one
architecture explicitly uses isize or usize and every other copy uses the same native-sized
integer for its architecture (i32/u32 on x86, i64/u64 on x64 or arm64), the callback keeps
one native-sized signature. It becomes arch-neutral when present on every merged architecture;
otherwise it keeps its subset arch tag. This handles SDK declarations such as FARPROC, whose
return is spelled INT_PTR on 64-bit but legacy int on x86. A plain i32/i64 pair does not
qualify: the explicit native-sized spelling is required as semantic evidence.
The merge is deterministic: it stages through BTreeMaps and insertion-ordered Vecs, with noHashMap reaching the output.
The merger and namespace remapper accept strings, Path, or PathBuf for input and output paths
and retain them as PathBuf. The remapper provides singular/plural input/inputs,source/sources, and route/routes methods.
Nested type identity
Decoded signature TypeName values retain the outermost namespace and a slash-separated name,
such as Test and Outer/Child. TypeDef::full_name, TypeRef::full_name, andTypeDefOrRef::full_name expose that identity; their name and namespace accessors still return
the raw table columns. TypeDef names follow NestedClass, while TypeRef names follow enclosing
TypeRef scopes.
The writer turns the qualified name into an enclosing-TypeRef chain. Merge preserves that chain,
and namespace remapping routes its outermost type without changing the nested path. ANestedClass row alone does not resolve an unqualified, module-scoped reference to a nested type
(ECMA-335 II.7.3 and II.22.38).
Nested generic instantiations are outside the writer's supported input. Both signature and
TypeSpec emission assert against them rather than derive a nested name's arity from the total
generic argument count. Top-level generics and non-generic nested records remain supported.
test_metadata::nested_references checks physical scope chains, signature decoding through both
TypeRef and TypeDef tokens, repeated leaf names under different parents, rejection of nested
generic instantiations, merge, and namespace remapping.
Method parameter association
ECMA-335 Param rows are not positional. Param.Sequence == 0 describes the return value, and a
nonzero sequence identifies a one-based signature parameter. Rows may be absent, sparse, or stored
out of order.
reader::MethodDef::params_by_sequence(parameter_count) returns a separate optional return row and
one Option<MethodParam> per signature position. It reports duplicate sequences and nonzero
sequences outside the signature as MethodParamSequenceError; consumers do not choose a
first-wins or last-wins interpretation. If several rows are invalid, the first invalid physical row
is reported. MethodDef::params() still iterates physical table order. The merge and remap writers
use that physical iterator on purpose so lossless copying preserves row order, sparse rows, and
malformed input for a later validator.
MethodParam::direction() decodes only the In and Out bits into ParamDirection::{Input, Output, InputOutput, Unspecified}. is_optional, is_reserved, and is_retval_attribute expose
their independent metadata facts. These helpers do not inspect pointer mutability, infer a default
for an unspecified direction, combine ReservedAttribute with Optional, or decide whether a
parameter should become a language return value. Array and byte-count attributes remain available
through attributes() because each projection validates different public-surface shapes.
Determinism and the winmd writer
The writer is the foundation of the pipeline's reproducible builds. It stages Constant /Attribute / GenericParam (and the bindgen TypeTree) in BTreeMaps / BTreeSets, and the
module MVID is a fixed zero GUID, so regenerating a winmd is byte-for-byte identical across
platforms (CI validates the committed winmds on Linux).
Notable invariants in the ECMA-335 tables:
- The
HasSemanticscoded index and theProperty/Event/MethodSemanticstables are emitted
only when non-empty, andMethodSemanticsrows are sorted byAssociation- strict readers
reject an unsorted table. write_indexasserts on the run-list one-past-end sentinel rather than silently wrapping to0
for a target table with exactly 65535 rows (the guard lives on the write side so the reader's
width threshold stays ECMA-conformant for external winmds).Value::Boolconstants round-trip symmetrically (the reader mapsELEMENT_TYPE_BOOLEAN); the CLR
element-type vocabulary (ELEMENT_TYPE_*) is hand-authored insrc/clr.rsbecause its defining
header (corhdr.h) ships only in the .NET SDK and cannot be scraped.
Testing
Run cargo test -p windows-metadata; see also the workspace test crates. The arch-merge
collapse/split rules are pinned by arch_roundtrip.rs (divergent fields, callbacks, forced
alignment, enum constant values, subset-present divergence) and merge.rs (native-sized callback
reconciliation). method_params.rs authors metadata directly with writer::File and covers dense,
absent, return, sparse, out-of-order, duplicate, and out-of-range parameter rows. It also covers all
four raw directions and verifies that optional, reserved, retval, and count attributes remain
independent facts. remap.rs covers explicit and fallback namespace routing, singular/plural
builder methods, missing outputs, and invalid inputs.
Crates Windows Numerics (`docs/crates/windows-numerics.md`)
windows-numerics
Graphics-oriented vector and matrix math from
Windows.Foundation.Numerics.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
windows-numerics provides the POD math value types used by Direct2D, composition, and other
graphics APIs: Vector2, Vector3, Vector4, Matrix3x2, and Matrix4x4. Each is a plain#[repr(C)] struct with inherent methods and operator overloads layered on top.
When to use
Use this crate when a Windows graphics or composition API acceptsWindows.Foundation.Numerics values. The structs have the expected Windows ABI layout, so vectors
and matrices can cross those API boundaries without conversion.
For a Rust-only math engine, consider a general-purpose math crate if you need quaternions, planes,
SIMD-specific operations, or a larger set of transforms. windows-numerics focuses on the five
value types projected by this crate.
Getting started
The crate README contains the dependency declaration and
minimal vector and matrix examples. A useful first workflow is to calculate with the same types
that the target Windows API expects:
use windows_numerics::{Matrix3x2, Vector2};
let position = Vector2::new(20.0, 30.0);
let offset = Vector2::new(5.0, -10.0);
let translated = position + offset;
let transform = Matrix3x2::translation(translated.x, translated.y);
assert_eq!((transform.m31, transform.m32), (25.0, 20.0));Keep values as f32; these Windows numeric types do not provide f64 variants.
Vectors
Vector2, Vector3, and Vector4 provide constructors, zero/one and axis vectors, dot products,
length and distance operations, and normalization. Vector3 also provides a cross product.
Arithmetic operators work with owned or borrowed vectors; multiplication and division between
vectors are component-wise. Scalar multiplication and division use f32.
Use length_squared or distance_squared when only a comparison is needed, avoiding a square
root. Check for a zero-length vector before calling normalize, since normalization divides by
the length.
Matrices and transforms
Matrix3x2 represents 2D affine transforms. It provides identity, translation, scale, rotation,
and skew constructors, including variants around a center point. Rotation and skew angles are in
degrees.
Matrix4x4 provides 3D translation, Y-axis rotation in degrees, and perspective projection.
Matrix multiplication composes transforms; preserve the order expected by the Windows API and test
the result with representative points rather than assuming another math library's convention.
Platform constraints and pitfalls
- The types are plain
#[repr(C)]structs with public fields. Initialize every field when using a
struct literal, or prefer the constructors. - Equality is exact floating-point equality. Use an application-appropriate tolerance for computed
values. - Normalizing a zero vector and constructing invalid projection inputs can produce non-finite
values. - The default
stdfeature supplies square root and trigonometric methods used by length,
distance, normalization, rotation, and skew. Basic value types and arithmetic remain available
without it.
Samples and next steps
The canvas, composition, Direct2D, and DirectComposition samples use these types in real graphics
workflows. Start with thecanvas transform sample for a 2D
matrix and thecomposition animation sample
for vectors passed to composition APIs.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-numerics.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/numerics.txt; the
inherent methods and operator trait impls are hand-written.
Testing
Run cargo test -p windows-numerics; see also the workspace test crates.
Crates Windows Pickers (`docs/crates/windows-pickers.md`)
windows-pickers
File and folder pickers backed by the Windows Common Item Dialog.
When to use it
Use windows-pickers when a Windows desktop application needs the system file-open, folder, or
file-save dialog. The crate returns filesystem PathBuf values and uses the Windows Shell for
navigation, filtering, recent locations, and overwrite confirmation.
The crate wraps the common picker workflows rather than every IFileDialog extension point. Use
raw Windows bindings when an application needs custom dialog controls, dialog events,
non-filesystem Shell items, or other advanced Common Item Dialog APIs.
The basic idea
Choose a picker, configure it with consuming builder methods, and show or request it:
| Type | Result |
|---|---|
OpenFilePicker |
One file, or several files through show_multiple |
FolderPicker |
One folder, or several folders through show_multiple |
SaveFilePicker |
A filesystem path at which to save a file |
Showing a picker is synchronous. IFileDialog::Show runs a nested modal message loop and returns
after the user confirms or cancels the dialog. The crate does not read, create, or modify the
selected file.
Choose how to host the picker
The default system feature accepts a borrowed windows_window::Window:
[dependencies]
windows-core = "0.100"
windows-pickers = "0.100"
windows-window = "0.100"Applications that own an HWND through another windowing library can disable default features and
use show_for_hwnd:
windows-pickers = { version = "0.100", default-features = false }Enable reactor to request a picker from a Reactor component:
windows-pickers = { version = "0.100", default-features = false, features = ["reactor"] }
windows-reactor = "0.100"The system and reactor features are additive. An application may enable both when it uses both
host types.
Pick your first file
This complete program initializes COM, creates an owner window, and shows an open-file picker:
use windows_pickers::{OpenFilePicker, Result};
use windows_window::Window;
fn main() -> Result<()> {
let _apartment = windows_core::init_sta()?;
let window = Window::new("Open a file").create()?;
let path = OpenFilePicker::new()
.title("Open a Rust source file")
.filter_extensions("Rust source", ["rs"])
.filter_all()
.show(&window)?;
if let Some(path) = path {
println!("{}", path.display());
}
Ok(())
}The calling UI thread must be a COM single-threaded apartment (STA). An application with an
existing UI framework normally uses the apartment initialized by that framework rather than
initializing COM again. Keep the owner window alive until show returns.
Pick files and folders
OpenFilePicker::show and FolderPicker::show return one selected path:
let file = OpenFilePicker::new().show(&window)?;
let folder = FolderPicker::new().show(&window)?;Use show_multiple when the user may select more than one item:
let files = OpenFilePicker::new()
.title("Attach files")
.commit_label("Attach")
.filter_all()
.show_multiple(&window)?;
let folders = FolderPicker::new()
.title("Add search folders")
.show_multiple(&window)?;commit_label changes the confirmation button for a task-specific action such as "Attach",
"Import", or "Choose".
Choose a save path
SaveFilePicker configures the proposed file name, extension, filters, and overwrite behavior:
let path = SaveFilePicker::new()
.title("Save report")
.filter_extensions("Text", ["txt"])
.suggested_name("report")
.default_extension("txt")
.overwrite_prompt(true)
.show(&window)?;suggested_name sets the initial name. default_extension supplies the extension when the user
enters a name without one; a leading period is optional. Configured filters restrict the accepted
file types. overwrite_prompt explicitly enables or disables replacement confirmation, while
leaving it unset preserves the native dialog default.
The returned path is only the user's choice. The application remains responsible for opening the
file and handling races, permissions, sharing violations, and any final overwrite policy.
Configure file filters
Add extension filters directly to an open or save picker:
let picker = OpenFilePicker::new()
.filter_extensions("Images", ["png", "jpg", "jpeg"])
.filter_extensions("Portable Network Graphics", ["png"])
.filter_all()
.initial_filter(0);Extensions may include a leading period. Use filter_patterns for native wildcard patterns:
let picker = OpenFilePicker::new()
.filter_patterns("Reports", ["report-*.csv", "summary-*.csv"])
.filter_all();Each filter must have a nonempty name and at least one nonempty extension or pattern.
initial_filter uses a zero-based index into the configured filters. An out-of-range index is
reported as an error before the dialog opens.
Set the initial location
Each picker provides two path-based and two known-folder methods:
| Method | Behavior |
|---|---|
default_folder(path) |
Used when the Shell has no remembered location |
folder(path) |
Forces the initially displayed folder |
default_location(location) |
Default using a PickerLocation |
location(location) |
Forced location using a PickerLocation |
PickerLocation supports Desktop, Documents, Downloads, Music, Pictures, Videos, Computer, and
3D Objects. When both a default and forced location are configured, the default is applied first
and the forced location determines the initial view.
Path-based locations must resolve to Shell items. An invalid or unavailable configured path is
reported before the dialog opens.
Preserve Shell state
Pass a stable GUID to client_id when the Shell should remember state such as the last visited
folder:
let picker = OpenFilePicker::new()
.client_id(GUID::from_u128(0x6d81d46c_77da_4874_8ca7_754546ba6803));Use the same GUID for picker uses that should share history and different GUIDs for independent
histories. Without a client ID, the crate does not opt the dialog into this persistence mechanism.
Handle cancellation and errors
Single-selection pickers return Result<Option>:
| Value | Meaning |
|---|---|
Ok(Some(path)) |
The user selected a path |
Ok(None) |
The user cancelled |
Err(error) |
Configuration, COM, or Shell operation failed |
Multiple-selection methods return Result<Vec>. An empty vector means cancellation
because the dialog cannot successfully confirm an empty selection. Only the nativeERROR_CANCELLED result is translated into cancellation; other failures remainwindows_core::Error values.
Use a raw HWND
The raw-handle methods work with another Win32 host:
let path = OpenFilePicker::new().show_for_hwnd(hwnd)?;The pointer must be a valid, non-null owner HWND on the calling UI thread and remain live until the
method returns. The calling thread must be a COM single-threaded apartment (STA). The raw methods
are available without the system feature.
Request a picker from Reactor
Picker methods cannot call IFileDialog::Show inline while Reactor is publishing component state.
The reactor feature adds request and request_multiple, which stage the modal operation and
map its result into a component message:
enum Message {
Open,
Picked(windows_pickers::Result<Option<PathBuf>>),
}
fn update(&mut self, message: Message, context: &ComponentContext<Self>) {
match message {
Message::Open => {
if !OpenFilePicker::new()
.filter_all()
.request(context, Message::Picked)
{
self.status = "Another window operation is pending".to_string();
}
}
Message::Picked(Ok(Some(path))) => {
self.status = path.display().to_string();
}
Message::Picked(Ok(None)) => {
self.status = "Selection cancelled".to_string();
}
Message::Picked(Err(error)) => {
self.status = format!("Picker failed: {error}");
}
}
}A true return means Reactor staged the request, not that the picker has already opened. Reactor
runs it on the owning window's UI thread after the current publication commits and queues the
mapped message for a later component update. It discards the operation if publication fails, the
component retires, or the window starts closing.
Each Reactor window accepts one pending native window operation at a time. Other Reactor windows
remain independent. No additional COM initialization is needed in a Reactor application. Thereactor/message-box sample uses the sameComponentContext::run_window support with another native modal API.
Samples
| Sample | What to study |
|---|---|
standalone |
All picker types with windows-window |
reactor |
All picker types through component messages |
Run an individual standalone workflow with:
cargo run -p pickers-standalone -- open
cargo run -p pickers-standalone -- open-multiple
cargo run -p pickers-standalone -- folder
cargo run -p pickers-standalone -- folder-multiple
cargo run -p pickers-standalone -- saveRun the Reactor sample with:
cargo run -p pickers-reactorInternal documentation
This section is for contributors to windows-pickers.
src/bindings.rs is generated by tool-bindings fromcrates/tools/bindings/src/pickers.txt. It contains the minimal Common Item Dialog, Shell item,
COM activation, and string-allocation APIs used by the hand-written wrapper.
Open and folder pickers create FileOpenDialog; the save picker creates FileSaveDialog. All
preserve the native option defaults while adding FOS_FORCEFILESYSTEM. Folder selection addsFOS_PICKFOLDERS, multiple selection adds FOS_ALLOWMULTISELECT, and save dialogs with filters
add FOS_STRICTFILETYPES.
Shared configuration retains UTF-16 backing storage until Show returns. Filesystem locations are
converted with SHCreateItemFromParsingName, known locations withSHCreateItemInKnownFolder, and selected IShellItem values with SIGDN_FILESYSPATH. Returned
strings are copied into PathBuf values before CoTaskMemFree.
Client identity is applied before folder configuration so a forced folder still determines the
initial view. Requested option bits are merged with GetOptions rather than replacing unrelated
native defaults.
After changing the binding filter, run:
cargo run -p tool-bindings --quiet
cargo check -p windows-pickers --quietUnit tests cover configuration and cancellation translation without opening modal Shell UI.
Crates Windows Rdl (`docs/crates/windows-rdl.md`)
windows-rdl
A parser for RDL (Rust Definition Language) and an ECMA-335 metadata generator.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
windows-rdl is the front of the metadata authoring pipeline. It parses RDL (Rust Definition
Language), a small Rust-like syntax for Windows APIs. It emits ECMA-335 .winmd metadata forwindows-bindgen. It also writes canonical RDL from .winmd files.
Most applications should use existing bindings rather than author metadata. Use windows-rdl when
you are defining a custom WinRT or Win32 surface, adapting header output fromwindows-clang, or maintaining reviewable text for a winmd generator.
Start with the crate README for setup and the two basic builder
calls. The full language reference is incrates/libs/rdl/rdl.md.
First workflow: define a custom component surface
- Create an
.rdlfile with one or more#[winrt]or#[win32]modules. - Add standard metadata as a reference if the declarations mention Windows types.
- Compile the RDL to a winmd with
reader. - Feed the winmd to
windows-bindgenor C++/WinRT. - Treat the RDL as the reviewed source and regenerate the binary winmd.
crates/samples/robot/component follows this path. Its build script compiles src/robot.rdl,
references standard Windows metadata, then asks bindgen for the Robotics namespace and WinRT
implementation support. crates/samples/robot/component_cpp sends the same kind of output tocppwinrt.
Input and output model
| Direction | Builder | Inputs | Output |
|---|---|---|---|
| RDL -> winmd | reader() |
.rdl files, directories, or text |
One .winmd file |
| winmd -> RDL | writer() |
.winmd files, directories, or bytes |
One file or partition |
Reader references resolve names but are not emitted as definitions. Use reference_default for
the bundled standard metadata, or the path and byte reference methods for custom dependencies.
Writer inputs are definitions to render; input_default renders the bundled metadata.
Writer filters accept namespace prefixes, qualified or unqualified names, and ! exclusions.split writes one file per namespace. partition accepts an item-name -> file-stem map and writes
one file per defining header. item_names reads the names declared under one namespace without
compiling the file.
RDL syntax
RDL uses Rust tokens and module syntax, but it describes metadata rather than executable Rust.
A top-level mod is a metadata namespace. Tag it #[winrt] or #[win32] to select the type
system. Items include classes, interfaces, delegates, callbacks, enums, structs, unions, typedefs,
constants, and imported functions.
#[win32]
mod Example {
#[repr(i32)]
enum Color {
Red = 1,
Green = 2,
Blue = 3,
}
struct Point {
x: i32,
y: i32,
}
const MAX: u32 = 42;
#[library("example.dll")]
extern fn GetPoint() -> Point;
#[guid(0x00000001_0002_0003_0004_000000000005)]
interface ICustom : Windows::Win32::System::Com::IUnknown {
fn Method(&self, value: i32) -> i32;
}
}Most attributes name a metadata attribute type directly. Some attributes use short pseudo-attribute
names. The reader expands those names to full metadata attributes. See PSEUDO_ATTRS inwindows-rdl.
Struct bit fields use their own syntax. A run of bit fields packed into one backing integer is
written as a C-like block on that field. Each member uses Name: width. Anonymous padding uses_: width.
struct D3D11_VIDEO_PROCESSOR_COLOR_SPACE {
_bitfield: u32 {
Usage: 1,
RGB_Range: 1,
YCbCr_Matrix: 1,
YCbCr_xvYCC: 1,
Nominal_Range: 2,
Reserved: 26,
},
}Member offsets are implicit. Each offset is the total width of earlier members, including padding.
The reader writes one Windows.Win32.Metadata.NativeBitfieldAttribute(name, offset, width) custom
attribute per named member. The writer renders it back to block form.
See windows-clang for how the scraper emits bit
fields. See windows-bindgen for the accessors they
drive.
WinRT types use the #[winrt] namespace flavor. They also add runtime-class and property syntax.
#[winrt]
mod Robotics {
#[Activatable(1)]
class Robot {
IRobot,
}
#[ExclusiveTo(Robot)]
interface IRobot {
fn Speak(&self, message: String);
Name: String;
}
}The crates/tests/libs/rdl/input directory has focused .rdl files for syntax examples. It covers
structs, flags, delegates, generic interfaces, unions, and more.
Common tasks and neighboring crates
The complete source-to-binding path is:
C/C++ headers -- clang() --> .rdl -- reader() --> .winmd -- bindgen() --> bindings.rs
(windows-clang) (windows-rdl) (windows-bindgen)- Skip RDL when a suitable winmd already exists.
- Use
windows-clangto create RDL from headers;tool-webviewdemonstrates the full path. - Use
windows-metadatafor table-level inspection, merge, and namespace remapping. - Use
writer().split()to maintain namespace-partitioned reviewable metadata. - Use
merge_arch_rdlonly for generators that have per-architecture RDL directories and winmds.
It merges structural differences and restores the defining-header partition. tool-reactor-metadatacompiles hand-authoredextras.rdlto fill metadata gaps before binding
WinUI.
Pitfalls
- RDL references must be supplied separately; naming an external type does not locate its winmd.
- The reader rejects unsupported types, constants, callback ABIs, variadic callback parameters,
and function ABIs instead of dropping them. - Pointer chains have one constness bit plus depth. Uniform
*mut *mut Tand*const *const Tchains work; mixed chains do not. len_paramandsize_paramstore raw zero-based signature positions. Update the attribute when
parameters move.- RDL cannot spell a metadata parameter with neither In nor Out. Omitting direction invokes the
type-based default. - Attributes on a void return row cannot round-trip because there is no return type to carry them.
splitandpartitionclear existing.rdlfiles in the output directory before writing.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-rdl.
How it's built
The RDL grammar uses syn, quote, and proc-macro2. It reuses Rust's tokenizer so the syntax
stays Rust-shaped. The reader lowers the syntax tree to ECMA-335 and emits .winmd throughwindows-metadata. The writer reads metadata through the same crate and
writes canonical RDL.
The clang path uses clang-sys to parse C or C++ translation units. It projects the declarations
into the RDL syntax tree. The header path and hand-authored RDL path share the same lowering code.
The formatter module pretty-prints generated RDL.
Testing
Dedicated test crates cover the crate:
test_rdlcovers RDL to winmd round trips withinput/*.rdlfixtures and invalid input witherrors/input/*.rdlanderrors/expected/*.txtdiagnostic fixtures.- The
windows-clangintegration tests cover header-to-RDL output. tool-roundtripre-derives committed RDL files from committed winmd files. Thegenworkflow
enforces a cleangit diff.test_bindgencovers the.winmdto Rust step that consumes this crate's output.
Run targeted tests with:
cargo test -p test_rdl
cargo test -p windows-clangDefault metadata files
windows-rdl builds the default metadata files used by the in-repo generators and library crates.
| File | Source | Writer |
|---|---|---|
crates/libs/default/Windows.winmd |
Merged SDK contract winmds | tool-winrt |
crates/libs/default/Windows.Win32.winmd |
Scraped SDK and WDK headers | tool-win32 |
The committed RDL files are the reviewable source for these metadata files:
metadata/winrt/*.rdlis partitioned by namespace.metadata/win32/*.rdlis partitioned by defining header.metadata/wdk/*.rdlis partitioned by defining header.
The binary winmd files are derived artifacts. Generation is deterministic. The metadata writer
stages tables in BTreeMaps and uses a fixed zero GUID for the module MVID.
Every maintained crate that needs Win32 metadata resolves against the in-repo Windows.Win32.winmd.
Minimal-binding crates and windows-reactor use it directly. The windows and windows-sys crates
use it through tool-package.
Multi-arch merge
tool-win32 scrapes x64, arm64, and x86 into separate RDL sets. Then merge_arch_rdl combines them
into one winmd. A type with the same shape on every architecture is emitted once. A type that
differs by architecture is split into per-architecture copies tagged #[arch(X86|X64|Arm64)].
The merge compares type structure through windows-metadata.merge_arch_rdl handles orchestration. It reads each architecture's RDL, runs the merge, and writes
the combined output. ArchInput stores its RDL directory and winmd as PathBuf.
Published crates and namespace remap
The in-repo Win32 and WDK metadata lives in flat namespaces. Published windows and windows-sys
APIs are partitioned behind many Cargo features.
tool-package remaps the flat metadata into header-stem namespaces under target/package/. It uses
the committed metadata/win32 RDL directory as the routing signal. Then it runs windows-bindgen
over that partition. tool-features uses the same remap so feature search reports the same header
stems.
The in-repo WinRT Windows.winmd is projected with the remapped Win32 and WDK metadata.
Round-trip rules
RDL is the reviewable source for WinRT, Win32, and WDK metadata. The .winmd files are derived
artifacts. The gen workflow runs the generators and tool-roundtrip. It fails when regeneration
changes tracked files.
| Family | External source | RDL layout | Winmd build path |
|---|---|---|---|
| WinRT | SDK winmds | metadata/winrt, per namespace |
merge -> RDL -> winmd |
| Win32 | SDK headers | metadata/win32, per header |
scrape -> arch merge -> winmd |
| WDK | WDK headers | metadata/wdk, per header |
scrape -> arch merge -> winmd |
tool-roundtrip validates the reverse direction:
- WinRT uses
writer(Windows.winmd).split()to writemetadata/winrt. - Win32 and WDK cannot recover header files from flat winmd alone. The tool reads the committed RDL
layout to map type names back to header stems. Then it writesmetadata/win32ormetadata/wdk
withwriter(winmd).partition(map).
Current normalization rules
Some metadata forms have canonical RDL spellings. These rules are intentional. They keep generated
RDL stable.
| Form | RDL spelling | Reason |
|---|---|---|
WinRT System.Char |
Char16 |
Keeps the type distinct from u16. |
| Property setter parameter | value |
Shorthand does not retain its original name. |
| Event add parameter | handler |
Shorthand stores the event shape. |
| Event remove parameter names | token |
The remove accessor takes an event token. |
| Property and event accessors | Shorthand | The writer tracks consumed accessor methods. |
| Other interface methods | Full method form | Non-accessor methods stay explicit. |
| Input direction | #[in] |
The reader accepts #[r#in]; the formatter emits #[in]. |
| Raw identifiers, GUIDs, delegate ABIs | Canonical output | Equivalent text has one spelling. |
The reader rejects unsupported forms with errors. It does not silently drop them. Examples include
unsupported types, constants, callback ABIs, variadic callback parameters, and function ABIs.
Inline nested records
Inline structs and unions have nested TypeDefs linked by NestedClass rows. Their field
signatures reference those definitions through enclosing-TypeRef chains, including every level
of nesting. The writer matches the qualified identity to the containing record and renders the
same inline syntax. It also accepts the unscoped leaf references in existing metadata.
test_rdl::nested_references checks physical TypeRef scopes before and after the text round trip,
alongside the canonical struct_nested_anon.rdl fixture. test_metadata::nested_references
independently covers the underlying signature reader, merge, and namespace remapper.
Method parameter rows
The winmd writer uses MethodDef::params_by_sequence fromwindows-metadata. Sequence 0 supplies return-type attributes. Nonzero rows
are matched to one-based signature positions, so sparse or out-of-order metadata cannot rename or
reflag another parameter. Every signature parameter is emitted; a missing row uses pN with no
explicit direction or optional marker, which the RDL reader lowers with its existing type-based
direction default. Duplicate and out-of-range sequences stop the write with a diagnostic.
Direction and optionality come from MethodParam::direction() and is_optional(). Reserved,
retval, and count attributes remain independent pseudos or custom attributes. The writer applies
RDL's format boundary only when spelling them: In+Out emits both markers, while Unspecified emits
the input spelling because RDL has no literal unspecified-direction syntax.
test_rdl::method_params authors sparse and out-of-order rows directly, checks parameter and
return pseudos in the generated RDL, and compiles the RDL back to dense metadata with the same
associations. It also checks that every non-return Param row in the committed WinRT, Win32, and
WDK metadata has at least one representable direction flag.
Lossless round-trip limits
- Direction flags: RDL has no spelling for a
Paramrow with neither In nor Out. Omitting both
invokes the type-based default, so a metadata row with neither flag reads back as In. - Void return rows: Return attributes are written after
->. A void method has no return type
to carry them, so attributes on its sequence 0 row are not represented. - Count relationships:
#[len_param(N)]and#[size_param(N)]store raw zero-based signature
positions. Reordering parameters without updatingNchanges the relationship. - Pointer constness:
metadata::Typestores one constness bit with a pointer depth. Uniform
chains such as*mut *mut Tand*const *const Tround-trip. Mixed chains are rejected before
metadata is written, including chains nested inside a reference.
Crates Windows Reactor Setup (`docs/crates/windows-reactor-setup.md`)
windows-reactor-setup
Build-time staging for self-contained Windows Reactor applications.
When to use it
Use windows-reactor-setup only when awindows-reactor executable must carry a private Windows App SDK runtime
beside the executable. It is a build dependency called from build.rs; it is not a runtime API and
does not belong in [dependencies].
Do not use it for a framework-dependent Reactor application. windows-reactor already contains
the framework bootstrap that resolves an installed Windows App SDK framework package at startup.
That deployment model stages no private runtime files.
Do not use it for a plain windows-webview HWND host. The WebView2 Evergreen runtime supplies
that COM-only path. Reactor's XAML WebView2 control has an additional projection DLL requirement,
which this setup helper handles for self-contained Reactor apps.
Prerequisites
- The Cargo target OS must be Windows.
- The build needs network access the first time each pinned NuGet package is staged.
%SystemRoot%\System32\curl.exeandtar.exemust be available.- The target must use MSVC, or the LLVM-based GNU ABI supported by the manifest linker arguments.
- Supported target architecture mappings are
x86->x86,aarch64->arm64, and other
Rust target architectures ->x64.
The README contains the build-dependency declaration and one-line build.rs.
First workflow: produce a self-contained build
- Add
windows-reactor-setupunder[build-dependencies]. - Create
build.rsand callwindows_reactor_setup::as_self_contained(). - Build the application normally with Cargo.
- Run the executable from its Cargo profile output directory to confirm the staged runtime is
used. - Package the executable together with all staged DLLs and runtime directories from that output
directory. Preserve their relative layout. - Test the packaged directory on a machine that does not provide the framework package expected
by a framework-dependent build.
The reactor/self_contained sample is the reference
project layout. The webview/reactor sample shows the same
setup for a Reactor app containing the XAML WebView2 control.
What the build step does
as_self_contained performs these operations during the application build:
- Resolve Cargo's profile output directory from
OUT_DIRandPROFILE. - Download and cache the pinned
Microsoft.WindowsAppSDK.RuntimeNuGet package. - Extract the MSIX for the target architecture.
- Copy the allow-listed Windows App Runtime files to the profile output directory.
- Download and cache the pinned
Microsoft.Web.WebView2NuGet package. - Copy the target architecture's
native_uap/Microsoft.Web.WebView2.Core.dllbeside the
executable. - Write an application manifest containing the self-contained deployment marker.
- Pass linker arguments that embed the manifest in binary targets.
Downloads and extracted packages are cached under%LOCALAPPDATA%\windows-reactor-setup\temp\v1 when LOCALAPPDATA is available. Cargo may rerun the
build script, but the cached package and extraction directories avoid downloading on every build.
The versioned cache does not reuse unchecked entries in the parent temp directory. Its first
use requires a fresh download. MSIX extraction caches are separate for each target architecture.
Deployment and shared target directories
The Cargo profile directory can contain outputs from several packages. Stage the application from
a clean, known build profile and copy every runtime file and subdirectory required beside the
executable. Copying only the .exe does not produce a self-contained deployment.
A destination-owned file lock serializes runtime and WebView2 copying by build scripts sharing
the same Cargo profile directory. Cache locks alone do not protect this shared output.
The embedded manifest includes a windows-reactor-self-contained description marker. Reactor reads
that marker to select the private runtime. A framework-dependent executable ignores private files
left by a self-contained build in the same Cargo target directory and uses its inlined framework
bootstrap instead.
Microsoft.Web.WebView2.Core.dll is always staged. This allows a self-contained app to addwindows-webview's reactor feature without another deployment step. It is the WinRT projection
assembly used by the XAML control, not the webview2loader.dll used by COM-only hosting.
Failures and cleanup
as_self_contained has no Result return. Unsupported target configuration, missing Cargo
environment variables, and staging errors panic during the build. Download, extraction, directory
access, and copy errors stop staging before the helper emits manifest linker arguments. Diagnostics
include the failing path or command.
Cache entries are published only after extraction succeeds and required files are present.
Failed or interrupted staging leaves an unpublished .partial directory, which the next attempt
discards and rebuilds. A failed copy can leave some files in the Cargo output directory, but the
build fails and the next attempt copies the runtime again.
The first build therefore requires reliable NuGet access. In offline build environments, populate
the helper's package cache before disconnecting or use a build environment with the required cache.
Do not delete the cache while another build is using it.
Staged files live in the Cargo profile output directory, outside the package's OUT_DIR.cargo clean removes the target output but not the package cache under LOCALAPPDATA. Remove that
specific cache directory manually only when forcing a fresh package download.
The helper copies fixed package versions. Update the crate rather than replacing staged files by
hand so the manifest, runtime allow-list, and WebView2 projection stay compatible.
Internal documentation
This section is for contributors to windows-reactor-setup.
as_self_contained validates CARGO_CFG_TARGET_OS, derives the target directory from the nearest
ancestor matching PROFILE, and falls back to the conventional ancestor depth. The unit tests
cover standard and split-package OUT_DIR layouts.
stage_pkg downloads a .nupkg inside a staging directory, extracts it, verifies the requested
architecture's MSIX or WebView2 DLL, and removes the archive before publishing the directory.
Runtime MSIX extraction uses .msix_extract-<arch> and checks for the core runtime and XAML DLLs
before publication.
cached_dir holds a file lock for each cache entry while checking, populating, and publishing it.
Extraction happens in a sibling .partial directory, followed by a rename to the final name.
The lock is released when its file handle is dropped, including after a failed or terminated build.
Concurrent builds share the completed entry rather than modifying the same staging directory.
copy_runtime_to copies only top-level entries named in assets/runtime.txt, then recursively
preserves any selected directory. Keep that allow-list in sync with the pinned Windows App SDK
runtime. Filesystem tests cover architecture isolation, failed extraction and retry, required-file
checks, concurrent cache publication, concurrent deployment processes, and copy failures.
Archive tests use local ZIP fixtures and the system tar.exe; they do not download packages or
launch WinUI.
The application manifest template is assets/app.manifest. The function inserts the deployment
marker after the opening assembly element, writes the result to OUT_DIR, and emits binary-only
manifest linker arguments for MSVC or LLVM GNU targets.
deploy_webview2 copies Microsoft.Web.WebView2.Core.dll from the pinned WebView2 package's
per-architecture native_uap directory. Keep its package version compatible with the WinRT
metadata and XAML WebView2 bridge used by windows-webview and Reactor.
Crates Windows Reactor (`docs/crates/windows-reactor.md`)
windows-reactor
A declarative WinUI 3 library built around components, typed messages, and native controls.
Windows Reactor lets Rust state drive native WinUI 3 controls. Start with a counter, then add
editable state, child components, and background work. Later sections cover common application
tasks and link to complete samples.
Reactor is currently a preview, so releases may still refine public APIs.
Create a project
Create a binary crate and add Reactor:
cargo new counter
cd counter
cargo add [email protected]Use the default features. An installed Windows App SDK runtime is required for the default
deployment model; see the setup guide for prerequisites.
For a GUI executable that should not open a console window, add this to the top of main.rs:
#![windows_subsystem = "windows"]Leave it out while diagnosing startup problems so console output remains visible.
Add a component
Replace main.rs with:
use windows_reactor::*;
#[derive(Clone, Copy)]
enum Message {
Increment,
Reset,
}
struct Counter {
count: i32,
}
impl Component for Counter {
type Input = ();
type Message = Message;
fn create(_input: &(), _context: &ComponentContext<Self>) -> Self {
Self { count: 0 }
}
fn update(&mut self, message: Message, _context: &ComponentContext<Self>) {
match message {
Message::Increment => self.count += 1,
Message::Reset => self.count = 0,
}
}
fn view(&self, _input: &(), context: &mut ViewContext<Self>) -> View {
let content = StackPanel::new().spacing(8.0).children((
format!("Count: {}", self.count),
Button::new()
.on_click(context.message(Message::Increment))
.content("Increment"),
Button::new()
.on_click(context.message(Message::Reset))
.content("Reset"),
));
context.window_frame("Counter", content)
}
}
fn main() {
App::run_component::<Counter>(()).unwrap();
}Run it:
cargo runThe component loop is:
native event -> typed message -> update state -> describe the next view| Method | Purpose |
|---|---|
create |
Build the initial state |
update |
Handle a message and change the state |
view |
Describe the controls for the current state |
view uses typed builders. StackPanel::children accepts a tuple of different control types, and
strings convert directly to text views. The button callbacks enqueue messages for update; they
do not mutate state inside the native event callback.
context.window_frame publishes the window title and returns the standard title-bar layout. For a
static view with no state or events, call App::run(view) instead.
Add editable state
To drive an editable control from component state, declare its current value and send changes
back as messages.
TextBox::new().text(self.name.clone())
.placeholder_text("Type your name")
.on_text_changed(context.callback(Message::SetName))Handle Message::SetName(name) in update by assigning self.name = name. Reactor suppresses
feedback from its own writes while still delivering user edits.
Only declared properties are controlled; subscribing to changes does not take ownership. Omitting
a previously declared property clears its native assignment once. An explicit None is a declared
value, not an omitted property. Store values in component state when they must survive control
replacement.
The controlled sample shows the complete pattern.
Split up the view
Use a function returning View for a stateless piece. Use a child component when a subtree needs
its own state or lifecycle:
View::component::<Greeting>(GreetingInput {
name: self.name.clone(),
})Component input must implement Clone + PartialEq.
Children at fixed positions keep identity by position and type. Items that can move need stable
application keys:
StackPanel::new().keyed_children(
self.tasks
.iter()
.map(|task| (task.id, task.title.clone())),
)Do not use list indices as keys when items can move. Stable keys keep state and focus attached to
the correct item.
Do work without blocking the window
Rendering and component updates run on the UI thread. Use set_timeout for delayed messages andspawn_background for blocking or CPU-intensive work. Background work returns a message rather
than touching controls or component state directly.
Use an effect when an external subscription or resource should exist only while part of the view
is present. See the use-effect andasync-state samples for complete lifecycle patterns.
Application state and notification icons
Use App::run_application::<A>(input) for application state that must survive closing individual
windows. Implement Application with create, update, and view, using typed messages and
callbacks as with Component. An ApplicationView declares windows and notification icons
without requiring a hidden application window.
| Declaration or command | Behavior |
|---|---|
view.window::<C>("main", input) |
Registers a reopenable window without opening it. |
context.show_window("main") |
Opens, restores, or activates that window; repeated requests coalesce. |
| Updated window input | Reaches the existing component without recreating its state. |
| User closes a window | Drops its component state; the declaration remains available to reopen. |
| Omit a window key | Closes its instance and removes its declaration. |
view.notify_icon("tray", icon) |
Creates or updates the keyed Shell registration. |
| Omit an icon key | Removes its registration and dismisses its menu. |
context.exit() |
Ends the application and releases all owned resources. |
Keys must be unique within each resource kind. A live window key must retain its component type.
Only keys declared in the application's view can be opened.
Construct a notification declaration with NotifyIcon::new(path), then attach tooltip,on_activate, and menu. The icon path names an .ico file. Activation callbacks receive aScreenPoint; attaching a Menu handles both mouse and keyboard context-menu positioning without
application-side coordinate conversion.
Windows and notification icons have independent lifetimes. The application keeps running while
either remains; registered but closed window slots do not keep it alive.
The notification icon sample demonstrates independent
window and icon lifetimes with typed inputs and callbacks.
The basic sample contains only a notification icon
and an Exit menu.
Notification menus follow the Windows taskbar theme. For a menu independent of a notification icon
or control, AppContext::show_menu_at accepts a Menu and physical screen coordinates.
Position and restore windows
Publish startup placement through context.window_visuals. The first committed declaration is
applied before the native window becomes visible, for both primary and secondary windows.
| Setting | Meaning |
|---|---|
initial_position(ScreenPoint) |
Initial outer-window top-left in physical screen pixels |
initial_placement(WindowPlacement) |
Restored outer bounds in physical screen pixels, plus maximized state |
client_size(width, height) |
Client-area size in DIPs; later explicit changes still resize |
constraints(WindowConstraints) |
Client-size limits in DIPs, including when restoring placement |
Full placement takes precedence over initial position and initial client size. Later changes to
either initial setting do not move or resize an open window. Load saved placement before opening
the window; an asynchronous update after startup is too late. Negative coordinates are valid on a
multi-monitor desktop. Windows adjusts fully off-screen bounds to an available monitor.
context.window_visuals(WindowVisuals::new().initial_placement(saved_placement));
context.on_window_placement(context.callback(Message::Placement));on_window_placement reports the initial placement and native changes while subscribed.
Notifications contain the restored outer bounds even when maximized and exclude minimized state.
Applications own persistence: save observations in the message handler rather than waiting for a
final shutdown notification. Keeping a saved value in initial_placement does not move the window
back after a user move.
on_color_scheme also reports the current value when subscribed; on_window_size reports changes
only. Replacing an observer callback does not replay the current value. Observers end when omitted
from the view or when their component closes.
Use WindowVisuals::theme to choose System, Light, or Dark. With no backdrop, transparent content
shows the window's themed background. Use WindowVisuals::backdrop to select Mica, MicaAlt, or
Acrylic; an opaque application background covers the material.
| Sample | Shows |
|---|---|
window-placement |
Open a window at an observed placement |
window-placement-registry |
Save placement across process restarts |
window-observers |
Subscribe to size, placement, and color-scheme changes |
window-theme |
Themes, backdrops, and application backgrounds |
Display images
Image and ImageIcon support these source builders:
| Builder | Source |
|---|---|
source(uri) |
A URI; returns a Result |
source_file(path) |
A file path; returns a Result |
source_data(bytes) |
Encoded bitmap bytes |
source_svg(bytes) |
SVG bytes |
Use EncodedImage::from_static for embedded assets or EncodedImage::new for owned bytes.
URI and file sources select SVG by the .svg extension. In-memory sources choose their decoder
through the builder:
# use windows_reactor::*;
let svg = EncodedImage::from_static(
br#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<circle cx="12" cy="12" r="10" fill="red"/></svg>"#,
);
StackPanel::new().children((
Image::new().source_svg(svg.clone()),
AppBarButton::new().icon(Icon::image_svg(svg)),
));Keep owned bytes in component state and clone the value when describing the view. Equal sources
share native images within a runtime. Image::on_opened and on_failed report loading outcomes
for each assignment, including when a shared source has already loaded.
The image-sources sample compares the source
builders across images and icon slots. image-reuse
shows page replacement with shared sources.
Animate a visual
Border pairs opacity, scale, and translation(x, y) with matchingopacity_transition, scale_transition, and translation_transition durations. Translation uses
DIPs and changes visual position without changing layout space.
New or replaced visuals start at their declared values without animating from native defaults.
Later transition settings apply before target changes in the same render, regardless of builder order.
Removing a target restores its default using the current transition. Removing a transition affects
future target changes; an animation already running can finish. Native transitions respect the
Windows animation setting. See the
translation sample for sliding and reversal.
Add icon content
Use Icon to supply a control's icon content:
# use windows_reactor::*;
StackPanel::new().children((
TitleBar::new()
.title("Search")
.icon(Symbol::Home)
.left_header(TextBlock::new().text("Files")),
AutoSuggestBox::new().query_icon(Icon::font("\u{E721}")),
));Use Icon::symbol, Icon::font, Icon::bitmap, Icon::image_*, or Icon::path for control icon
slots. Use SymbolIcon, FontIcon, BitmapIcon, ImageIcon, and PathIcon when the icon is a
standalone visual that needs layout or other visual properties.
Choose a deployment model
A framework-dependent application uses an installed Windows App SDK framework package and needs
only windows-reactor.
For a self-contained application, add:
[build-dependencies]
windows-reactor-setup = "0.100"Then create build.rs:
fn main() {
windows_reactor_setup::as_self_contained();
}The setup guide covers prerequisites, WebView2, and packaging.
Explore larger samples
| Sample | What it demonstrates |
|---|---|
calculator |
State, messages, and Grid layout |
solitaire |
A larger application with keyed state |
stacker |
Reactor, Canvas, and Composition together |
gallery |
Available controls and navigation |
Browse the sample directory for smaller examples of controlled
input, context, effects, multiple windows, virtualization, WebView2, and deployment.
The navigation sample demonstrates pane
content and visibility settings.
Contributing
Controls are generated from crates/tools/reactor/src/schema.toml and the committed WinUI metadata.
Edit the schema or generator, not the generated files. tool-reactor-metadata refreshes the
metadata; tool-reactor generates declarations, native adapters, bindings, and control coverage.
The native binding filter is crates/tools/reactor/src/bindings.txt.
Use metadata names unless a shared Reactor name matches the public value, role, and behavior.
Container names distinguish one owned visual (content), ordered visuals (children), and item
declarations (items).
After a generator or schema change, regenerate and inspect the output:
cargo run -p tool-reactor --quiet
cargo check -p windows-reactor --quiet
cargo test -p windows-reactor --all-features --libChanges to the public API also require updating public-api.txt.
Crates Windows Reference (`docs/crates/windows-reference.md`)
windows-reference
A stock implementation of the WinRT
IReference<T>interface.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
When to use this crate
Use windows-reference when a WinRT signature explicitly requires IReference<T> and you need to
box a Rust value for that call or component boundary. WinRT uses this interface for boxed value
types, similar to Nullable<T> in other projections.
Do not use it for ordinary Rust borrowing. &T, Box<T>, and Option<T> express Rust ownership
and optionality without creating a WinRT object. Add this crate only at an interop boundary that
names IReference<T>.
Getting started
The crate README has the dependency declaration and
minimal boxing example. The public implementation is available on Windows with the default std
feature. The value type must implement RuntimeType + Clone + 'static.
The first workflow is to convert a value with IReference::<T>::from, pass the resulting projected
interface to the WinRT API, and call Value when reading one received from an API:
use windows_reference::IReference;
fn round_trip() -> i32 {
let boxed = IReference::<i32>::from(42);
boxed.Value().unwrap()
}In application code, propagate the Result from Value instead of unwrapping when the interface
call can fail.
Core API model
IReference<T> is a reference-counted WinRT object. Creating one clones or moves the Rust value
into a stock implementation, and each call to Value returns a clone. Cloning theIReference<T> itself clones the interface reference, not the boxed value.
The object implements the WinRT identity and runtime type information forWindows.Foundation.IReference<T>. It also supports IPropertyValue for scalar value types, but
the public Rust wrapper exposes Value as the supported way to retrieve the boxed value.
Common tasks
Box scalar and string values
use windows_reference::IReference;
use windows_strings::HSTRING;
let count = IReference::<u32>::from(3);
assert_eq!(count.Value().unwrap(), 3);
let label = IReference::<HSTRING>::from("ready");
assert_eq!(label.Value().unwrap(), "ready");IReference<HSTRING> accepts HSTRING, &HSTRING, String, and &str. Other supported value
types use their normal From<T> conversion. Add windows-strings directly when the surrounding
crate does not already provide its string types.
Use a boxed value in a component
Keep IReference<T> in the component signature when the metadata declares a boxed value. Accept
the interface by the projected parameter form, call Value once, and continue with the plain Rust
value. When returning one, construct it at the return boundary:
use windows_reference::IReference;
fn boxed_count(value: u32) -> IReference<u32> {
IReference::from(value)
}This keeps WinRT reference counting at the boundary and avoids repeated ABI calls in the rest of
the implementation.
Important choices and pitfalls
IReference<T>boxes a present value. Optionality is represented by whether the interface
itself is present in the surrounding projected signature.Valueis fallible because it is a WinRT interface call, even for an object created locally.- The value is cloned into and out of the stock object. Avoid using it as a high-frequency wrapper
for large values when a different API shape is under your control. - The generic type determines the interface IID and must match the receiving signature exactly.
IReferenceandIReferenceare different WinRT interface types. - Array accessors on the object's internal
IPropertyValueimplementation are not supported.
Use the typedValuemethod.
Samples and next steps
The reference-reference sample boxes scalar and
string values and reads them through IReference<T>. For related interop patterns, seewindows-collections for collection interfaces and therobot sample for a component boundary implemented in Rust.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-reference.
How it's built
src/bindings.rs is generated; the IReference<T> implementation is hand-written.
Testing
Run cargo test -p windows-reference; see also the workspace test crates.
Crates Windows Registry (`docs/crates/windows-registry.md`)
windows-registry
Simple, safe, and efficient access to the Windows registry.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
windows-registry wraps the Win32 registry APIs behind a small, safe surface. Start from one of the
predefined roots, then open a key and work with typed values.
When to use
Use this crate when a Windows application must read or write registry configuration and wants owned
keys, typed values, iteration, transactions, or explicit registry-view selection. Use a portable
configuration format when settings must work on non-Windows systems or travel with the application.
Getting started
The crate README has the dependency declaration and minimal
read/write and transaction examples. For a first workflow, read application settings underCURRENT_USER and propagate missing-key, access, type, and data errors to the layer that can decide
how to handle them:
use windows_registry::{CURRENT_USER, Result};
fn read_launch_count() -> Result<u32> {
let key = CURRENT_USER.open(r"Software\Contoso\Widget")?;
key.get_u32("LaunchCount")
}Create keys during installation or first-run setup, and remove test data when finished.
Keys and access
The predefined roots include CLASSES_ROOT, CURRENT_CONFIG, CURRENT_USER, LOCAL_MACHINE, andUSERS. open requests read access. create opens or creates a key with read and write access.
Both return an owned Key that closes its handle on drop.
Use options() when the defaults are not appropriate:
use windows_registry::{CURRENT_USER, Result};
fn open_for_update() -> Result<windows_registry::Key> {
CURRENT_USER
.options()
.read()
.write()
.wow64_64()
.open(r"Software\Contoso\Widget")
}The builder can add access rights, create a volatile key, or associate the operation with aTransaction.
Reading, writing, and enumerating
Typed helpers cover u32, u64, strings, expandable strings, multi-strings, and binary data.get_value and set_value preserve a value's registry Type when the typed helpers are not
enough. keys() enumerates child names and values() enumerates names and values.
Registry operations are not atomic as a group. To update several keys together, create aTransaction, open or create each key through options associated with it, and call commit.
Dropping an uncommitted transaction rolls it back.
Platform constraints and pitfalls
- The crate is Windows-only. Access to protected locations such as parts of
LOCAL_MACHINE
depends on the process token and may require elevation. - A 32-bit process and a 64-bit process can see different redirected views. Select
wow64_32()orwow64_64()when both processes must address a known view. They are mutually exclusive; the last
call wins. - Expandable strings are returned as stored. Reading one does not expand environment variables.
- Value names may be empty for a key's default value.
remove_treeis recursive. Scope the parent and path carefully before deleting.- Kernel Transaction Manager support and policy can vary by Windows environment. Surface
transaction creation and commit failures to the caller.
Samples and next steps
The read-write sample creates, reads, and removes a
test key. The transaction sample performs the write
through a transaction.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-registry.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/registry.txt;Key, Value, OpenOptions, and Transaction are hand-written safe wrappers.
Testing
Run cargo test -p windows-registry; see also the workspace test crates.
Crates Windows Result (`docs/crates/windows-result.md`)
windows-result
Efficient Windows error handling and propagation for Win32, COM, and WinRT.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
When to use this crate
Use windows-result when code works directly with Windows status values or exposes Windows errors
without depending on the larger windows-core crate. Generated windows-* APIs normally return
these types through a re-export, so application code often does not need a separate dependency.
Choose this model when callers need the original HRESULT, Win32 error code, or COM error
information. Convert to a domain-specific error at your application boundary when callers do not
need the Windows details.
Getting started
The crate README contains the dependency declaration and a
minimal example. A typical first workflow is to keep windows_result::Result<T> as the return type,
convert the status value immediately after a Windows call, and use ? for propagation:
use windows_result::{HRESULT, Result};
fn check_status(status: HRESULT) -> Result<()> {
status.ok()?;
Ok(())
}On Windows, status-to-message conversion asks the operating system for descriptive text. The core
status and error types also compile without std; integrations such as std::error::Error andstd::io::Error require the default std feature.
Core API model
| Type | Success rule | Typical source |
|---|---|---|
HRESULT |
Any value >= 0 | COM and WinRT methods |
BOOL |
Any nonzero value | Win32 functions with GetLastError on failure |
WIN32_ERROR |
Zero | GetLastError and Win32 error constants |
NTSTATUS |
Any value >= 0 | Native system APIs |
RPC_STATUS |
Zero | RPC APIs |
Error |
Always represents an error | Rich Rust error with code and optional error info |
Result<T> |
Result<T, Error> |
Windows operations exposed to Rust |
The wrappers preserve the status representation until it needs to become an Error. Their ok
methods apply the correct success rule, and conversions normalize failures to an HRESULT.
Common tasks
Convert a status and preserve its code
use windows_result::{Error, Result, WIN32_ERROR};
fn cancelled() -> Result<()> {
let code = WIN32_ERROR(1223);
Err(Error::from(code))
}
let error = cancelled().unwrap_err();
assert_eq!(WIN32_ERROR::from_error(&error), Some(WIN32_ERROR(1223)));WIN32_ERROR::to_hresult uses the standard FACILITY_WIN32 mapping. UseWIN32_ERROR::from_error when you need to recover a mapped Win32 code.
Attach context to a failure
Use Error::new when you are creating the failure and have a useful message:
use windows_result::{Error, WIN32_ERROR};
let code = WIN32_ERROR(1223).to_hresult();
let error = Error::new(code, "operation cancelled");
assert_eq!(error.code(), code);
assert_eq!(error.message(), "operation cancelled");Error::from_hresult stores only the code. Converting an HRESULT with Error::from can capture
thread-local COM error information when it is available. Error::message falls back to the system
description for the code.
Work with last-error APIs
Call BOOL::ok, WIN32_ERROR::from_thread, or Error::from_thread immediately after the failing
Windows call. Another Windows call may overwrite the thread's last-error value.
use windows_result::{BOOL, Result};
fn check_win32_result(value: BOOL) -> Result<()> {
value.ok()
}Only use this pattern for APIs whose documentation says failure details come from GetLastError.
Some Win32 functions use other return conventions.
Important choices and pitfalls
HRESULT(1)is success, not failure. Test it withis_okorokrather than comparing with
zero.BOOL(0).ok()reads the current thread's last-error value. Do not construct a falseBOOLlater
and expect it to retain the original error.Errorequality and ordering compare theHRESULT, not the message or COM error information.- Converting a mapped Win32
Errortostd::io::Errorrecovers the underlying Win32 code.
Other facilities retain the fullHRESULTas the raw OS error. --cfg=windows_slim_errorsis a whole-program policy that stores only the four-byteHRESULT.
It reduces error size but discards extended COM and WinRT error information.
Samples and next steps
The result-result sample creates an error with a message,
propagates status failures, and inspects the resulting error. Run it from the workspace with:
cargo run -p result-resultFor error handling alongside projected COM types, continue withwindows-core.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-result.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/result.txt. Thecom, bstr, and strings modules add COM error-info support and are gated on windows (and
disabled under the windows_slim_errors cfg).
Testing
Run cargo test -p windows-result; see also the workspace test crates.
Crates Windows Services (`docs/crates/windows-services.md`)
windows-services
A simple, safe way to implement Windows services in Rust.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
windows-services lets you write a Windows service process with a builder. Use Service::new to
declare accepted controls, then run to connect the process to the Service Control Manager (SCM).
When to use
Use this crate to implement the runtime side of a Windows service: service startup, status changes,
and control callbacks. It does not install, configure, start, or remove the service registration.
Use the SCM command-line tools or an installer for that lifecycle.
Use a normal console application for foreground tools and short-lived scheduled work. A service is
appropriate when Windows must manage a long-running background process and deliver service control
events.
Getting started
The crate README has the dependency declaration and minimal
builder example. The first practical workflow is:
- Build a service executable.
- Register its absolute path with the SCM.
- Start it through the SCM, not by launching it directly.
- Handle
Command::Startand every control enabled on the builder. - Stop and remove the test registration when finished.
The simple sample prints the correspondingsc create, sc start, sc query, sc stop, and sc delete commands when run outside the SCM.
Commands and state
Service::new accepts only startup by default. can_stop enables stop and shutdown controls.can_pause enables pause and resume. can_accept(mask) enables other SCM control classes, which
arrive as Command::Extended.
The callback receives &Service and a Command. The crate moves through the normal pending and
stable states around start, stop, pause, and resume callbacks. Use state() to inspect the current
state and set_state() only when the service needs to report a state outside that automatic flow.
Keep the callback responsive. Start worker threads or signal existing workers instead of doing
lengthy work on the control callback. On Stop, signal shutdown and release resources promptly.
Running outside the SCM
Without configuration, run returns an error when the process was not started by the SCM.can_fallback installs a closure for intentional console or development execution. In fallback
mode the crate sends start and stop commands around that closure, allowing one executable to share
service setup with a local test path.
Platform constraints and pitfalls
- The crate and service process are Windows-only.
- Registering or changing a service normally requires administrative rights.
runowns the service dispatch lifecycle. Arrange application initialization before calling it.- Enable only controls the callback handles. Other accepted controls arrive with raw event data in
ExtendedCommand; interpret that pointer only according to the matching Windows control code and
during the callback. - A service normally has a different working directory, environment, account, and desktop access
than an interactive process. Use absolute paths and avoid assumptions about an interactive user. - Log to the Windows Event Log or another service-safe destination rather than standard output.
Samples and next steps
Start with the simple sample. Thetime-change sample shows can_accept, an extended
time-change control, and decoding its event data with the matching Win32 definitions.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-services.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/services.txt; the
service runtime and dispatch glue are hand-written.
Testing
Run cargo test -p windows-services; see also the workspace test crates.
Crates Windows Strings (`docs/crates/windows-strings.md`)
windows-strings
The string types used across the Windows APIs.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
When to use this crate
Use windows-strings when a Windows ABI requires a Windows string type and you do not otherwise
need windows-core. Most projected APIs re-export these types, so use the type named by the
function signature instead of converting every string to one preferred representation.
The main choice is ownership: HSTRING and BSTR own their contents, while PCSTR, PCWSTR,PSTR, and PWSTR are pointer wrappers whose storage belongs elsewhere.
Getting started
The crate README has the dependency declaration and a
minimal example for each common representation. Before the first API call, inspect its signature:
- Use
HSTRINGfor WinRT strings. - Use
BSTRfor COM Automation strings. - Use
PCWSTRorPCSTRfor borrowed, null-terminated input. - Use
PWSTRorPSTRonly when the API writes through the pointer.
HSTRING, the pointer wrappers, and the literal macros are available across targets. BSTR is
Windows-only because allocation and deallocation use the COM Automation ABI. Conversions involvingOsStr, OsString, or Path require Windows and the default std feature.
Core API model
| Type or macro | Encoding and ownership | Common use |
|---|---|---|
HSTRING |
Owned, immutable, reference-counted UTF-16 | WinRT parameters and results |
BSTR |
Owned, length-prefixed UTF-16 | COM Automation parameters and results |
PCWSTR / PWSTR |
Borrowed const / mutable UTF-16 pointer | Win32 wide-string ABIs |
PCSTR / PSTR |
Borrowed const / mutable 8-bit pointer | Win32 8-bit string ABIs |
h! |
Static &HSTRING literal |
Reused WinRT string constant |
w! |
Static null-terminated PCWSTR literal |
Wide input constant |
s! |
Static null-terminated UTF-8 PCSTR literal |
8-bit input constant |
HStringBuilder |
Preallocated mutable UTF-16 buffer | Build one immutable HSTRING |
Length-prefixed types can represent data independently of a trailing null. Pointer types depend on
a null terminator and do not record a length or lifetime.
Common tasks
Create owned strings
use windows_strings::{BSTR, HSTRING};
let runtime_name = HSTRING::from("Windows.Foundation.Uri");
assert_eq!(runtime_name, "Windows.Foundation.Uri");
#[cfg(windows)]
{
let automation = BSTR::from("report");
assert_eq!(automation, "report");
}HSTRING::from_wide accepts UTF-16 code units directly. to_string_lossy and display are useful
when diagnostics should continue through invalid UTF-16; equality against str compares the
encoded contents without first allocating a Rust String.
Pass string literals
use windows_strings::{h, s, w, HSTRING, PCSTR, PCWSTR};
const UTF8_NAME: PCSTR = s!("name");
const WIDE_NAME: PCWSTR = w!("name");
let runtime_name: &HSTRING = h!("Windows.Foundation.Uri");The literal macros append the required terminator at compile time. h! returns a borrowed staticHSTRING; clone it or construct HSTRING::from only when an owned value is required.
Read a pointer returned by Windows
Pointer conversion is unsafe because the wrapper cannot prove that the pointer is non-null, valid,
terminated, or still alive:
use windows_strings::PCWSTR;
unsafe fn copy_wide(value: PCWSTR) -> Result<String, std::string::FromUtf16Error> {
unsafe { value.to_string() }
}Check the producing API's ownership rules before reading or freeing its pointer. A null pointer is
not an empty string: calling len, as_wide, as_bytes, or to_string on it is invalid.
Fill an `HSTRING` buffer
Use HStringBuilder::new when another operation fills a known number of UTF-16 code units. Write
through its mutable slice, call trim_end if unused trailing elements remain zero, and convert the
builder into HSTRING.
Important choices and pitfalls
- Prefer wide Windows APIs when both 8-bit and wide variants exist. A
PCSTRtype alone does not
define which legacy code page a particular Win32 function applies. PCSTR,PCWSTR,PSTR, andPWSTRdo not own their buffers. Keep the backing allocation alive
for the entire call.- Do not pass an immutable literal through
PSTRorPWSTR; writable pointer types require
writable storage sized as the API specifies. - Use
BSTRonly for APIs that transfer or borrow a COM Automation string according to the stated
ownership contract. - The default
stdfeature adds standard-library conversions. Disable it only if the remainingalloc-based API meets the target's needs.
Samples and next steps
The string samples cover HSTRING, BSTR, and the wide and 8-bit literal macros. Run them from the
workspace with:
cargo run -p strings-hstring
cargo run -p strings-bstr
cargo run -p strings-wide-ansiContinue with windows-core when the string is part of a projected COM or WinRT
call.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-strings.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/strings.txt; the
string types and macros are hand-written.
Testing
Run cargo test -p windows-strings; see also the workspace test crates.
Crates Windows Sys (`docs/crates/windows-sys.md`)
windows-sys
Raw Windows API declarations, constants, and ABI types.
- 📦 crates.io
- 📖 API reference
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
windows-sys projects Windows metadata as raw FFI. Functions keep pointer parameters and native
integer conventions, structs are plain ABI values, and the crate does not provide owning COM or
WinRT wrappers.
When to use it
Prefer a focused crate when one covers the task. For APIs not covered there, a reusable library
should generate a narrow private binding set with windows-bindgen.
Choose windows-sys when a binary application wants a pre-generated broad API and its Rust layer
should stay close to a C signature, such as:
- sharing layouts and callbacks with existing C or C++ code;
- writing a narrow wrapper where ownership and error translation are handled locally;
- calling a raw API shape that the rich projection cannot represent.
Choose windows when a binary application needs a similarly broad projection with
reference-counted interfaces, parameter conversion traits, and projected results.
Reusable libraries should avoid both umbrella crates. They add dependency weight and version
churn, and several libraries can bring incompatible versions into one dependency tree. Cargo
features limit what compiles, but they do not remove those dependency-management costs.
Do not choose windows-sys on the assumption that windows adds runtime overhead to every call.
The meaningful difference is API shape and who must maintain the unsafe contract.
Features and API discovery
The same metadata drives windows and windows-sys, but raw Win32 feature names usually follow
lowercase header groups. For example, a program using event handles may need synchapi,handleapi, and type features such as winnt or minwinbase.
Use the API reference and the generated signature:
- Search for the native API name.
- Confirm that the result is available in
windows-sys; rich-only COM and WinRT projections do
not have equivalent raw interface wrappers. - Enable the feature shown for the function and any feature needed by types in its signature.
- Inspect compiler errors before adding broad features. Generated feature dependencies already
include many prerequisites. - Verify operating-system availability separately from crate availability.
Keep imports explicit at an FFI boundary. windows_sys::Win32::* is convenient in a small sample,
but named imports make it clear which constants, structs, and extern functions a wrapper exposes.
Raw ABI patterns
The core aliases show the intended level of abstraction:
BOOL,HRESULT,NTSTATUS, andRPC_STATUSare integer aliases.PCSTRandPCWSTRare const pointers.PSTRandPWSTRare mutable pointers.BSTRandHSTRINGare raw ABI handles, not owning Rust strings.IUnknown_VtblandIInspectable_Vtbldescribe base vtable layouts.
Generated functions do not add Option, slices, Rust bool, automatic out values, or resource
guards. Pass null pointers, lengths, discriminants, and callbacks in the exact form required by the
Windows documentation. Callback functions generally need extern "system" and must obey the
documented lifetime and threading rules.
Use #[repr(C)] for your own structs that cross the boundary. Do not transmute betweenwindows-sys and windows types merely because their current layouts appear equal. Convert
through documented raw representations at one reviewed boundary.
Safety and ownership
Treat each extern call as a three-part contract:
- Inputs must remain valid for the duration documented by the API.
- The return value must be interpreted using that API's success convention.
- Every acquired resource must be paired with its documented release operation.
Raw handle aliases are often pointers, but pointer shape does not establish ownership. A returned
value may be borrowed, process-owned, closed with CloseHandle, released by another function, or
compared with a special sentinel. Build a private RAII guard as soon as an owned resource enters
Rust, and transfer ownership explicitly when the Windows API takes it.
Keep unsafe inside a safe wrapper wherever the contract can be expressed and checked in Rust.
Validate buffer sizes and integer conversions before the call. Avoid constructing slices from raw
pointers until nullability, alignment, initialization, and element count have all been established.
Errors
windows-sys returns native status values. It does not translate them intowindows_core::Result:
- For
BOOL, check against zero only when the API documents zero as failure. - For handles, distinguish null from
INVALID_HANDLE_VALUE; different functions use different
sentinels. - For HRESULT values, use the documented success and failure ranges rather than treating every
nonzero value as an error. - For last-error APIs, read
GetLastErrorimmediately after the failing return and before another
Windows call. - Some APIs set last error only for selected failures. Do not report a stale last-error value when
the API does not promise one.
A wrapper may translate these results into its own error type or use the focusedwindows-result crate. Keep the native status available when callers need to
make API-specific decisions.
Strings and buffers
The s! and w! macros create null-terminated literal pointers. They do not allocate dynamic
storage. For runtime UTF-16 input, encode into a Vec, append a zero, and keep the vector
alive and unchanged until the call returns. Reject or handle interior nulls according to the target
API.
Output strings require the pattern documented by the function. Common forms include a caller-owned
buffer and length, a size query followed by allocation, or a pointer allocated by Windows and
released by a specific function. Do not assume that PWSTR implies any one of these forms.
Lengths may count bytes, code units, characters, or include the trailing null. Preserve the native
unit in variable names and check conversions to the API's integer width.
A practical boundary
A maintainable raw wrapper usually follows this order:
- Define the smallest public Rust operation needed by the application.
- Import only the native items needed for that operation.
- Convert Rust values and establish pointer lifetimes.
- Make one or a small group of related calls inside a narrow
unsafeblock. - Capture status or last error immediately.
- Put owned resources in guards before any fallible follow-up work.
- Convert outputs into owned Rust values before returning.
The windows-sys service sample is useful for callback and struct layout patterns, but production
code should also check every fallible API result and guard each acquired handle.
Internal documentation
The remainder of this page is for repository contributors.
How it is built
tool-package generates the crate by driving windows-bindgen in --sys --package mode fromcrates/tools/package/src/sys.txt. src/lib.rs and the small src/core ABI module are
hand-maintained; the Windows namespace files and Cargo feature graph are generated.
Sys package generation prunes namespaces that would contain no raw items. In particular, a
namespace containing only projected COM interfaces does not produce an empty module. Generated
extern functions link through windows-link.
Do not edit generated source under crates/libs/sys/src/Windows. Change metadata, filters, or the
generator and run the appropriate repository tool.
Testing
Run cargo test -p windows-sys; the workspace test crates and windows-sys samples cover wider ABI
usage.
Crates Windows Threading (`docs/crates/windows-threading.md`)
windows-threading
Simple, safe access to the Windows thread pool.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
windows-threading exposes the Windows thread pool through a small, safe API. submit runs a
closure on a pool thread, while Pool provides ownership and sizing controls.
When to use
Use this crate for CPU or blocking work that should run on the Windows thread pool without bringing
in a Rust async runtime. It is also useful when scoped tasks need to borrow local data.
Use std::thread when each task needs a dedicated thread, thread-local initialization, or a stable
thread identity. Use an async runtime's task APIs when the application already depends on that
runtime and the work is primarily asynchronous I/O.
Getting started
The crate README contains the dependency declaration and
minimal examples for all three submission styles. For a first workflow, decide whether the caller
must wait:
let inputs = [2, 3, 4];
let outputs = std::sync::Mutex::new(Vec::new());
windows_threading::Pool::with_scope(|scope| {
for input in inputs {
let outputs = &outputs;
scope.submit(move || outputs.lock().unwrap().push(input * input));
}
});
assert_eq!(outputs.lock().unwrap().len(), inputs.len());with_scope does not return until its submissions finish, so the closures can borrow inputs andoutputs.
Choosing a submission API
| API | Waiting and lifetime behavior |
|---|---|
submit |
Uses the process-wide default pool and returns immediately; the closure is 'static. |
for_each |
Submits every iterator item in parallel and waits for all of them. |
Pool::submit |
Uses a private pool and returns immediately; the closure is 'static. |
Pool::scope |
Accepts borrowing closures and joins the pool before returning. |
Dropping a Pool also waits for its outstanding submissions. Calling join waits explicitly.scope and join wait for all work on that private pool, including work submitted from another
thread.
Pool sizing and task design
Windows chooses the private pool size unless set_thread_limits(min, max) is called. Set limits
only when the workload has measured concurrency requirements. Keep pool closures finite, avoid
waiting on other work queued to the same constrained pool, and protect shared mutable state with
normal synchronization primitives.
thread_id() returns the current Windows thread identifier. sleep(milliseconds) blocks the
current worker; it does not schedule a timer or yield an async task.
Platform constraints and pitfalls
- This crate wraps the Windows thread-pool APIs and is for Windows targets.
submitandPool::submitcannot borrow stack data because they may outlive the caller. UsePool::scopefor borrowed data.- A panic in a submitted closure crosses a system callback boundary and must not be used as an
error-reporting strategy. Handle expected failures inside the closure. - Thread-pool allocation failures are treated as unrecoverable and panic.
Samples and next steps
The for-each sample shows parallel aggregation. Thepool sample shows thread limits, scoped borrowing, and how
work is distributed among pool threads.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-threading.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/threading.txt;Pool and the submit/for_each helpers are hand-written.
Testing
Run cargo test -p windows-threading; see also the workspace test crates.
Crates Windows Time (`docs/crates/windows-time.md`)
windows-time
WinRT
TimeSpanandDateTimewith idiomatic Rust conversions.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
windows-time provides the two WinRT time primitives as plain #[repr(C)] Rust types: TimeSpan
(a duration) and DateTime (a point on the WinRT UTC timeline).
When to use
Use these types when exchanging time values with WinRT APIs or when code benefits from their signed,
100-nanosecond representation. Use std::time::Duration for nonnegative Rust-only durations andstd::time::SystemTime when no Windows ABI value is needed.
Getting started
The crate README has the dependency declaration and a minimal
arithmetic example. A practical workflow is to keep calculations in DateTime and TimeSpan, use
checked arithmetic at input boundaries, and convert only where another API requires a std type:
use windows_time::{DateTime, TimeSpan};
let started = DateTime::now();
let timeout = TimeSpan::from_seconds(30);
let deadline = started.checked_add(timeout).expect("deadline out of range");
assert!(deadline > started);`TimeSpan` durations
Construct spans from ticks, microseconds, milliseconds, seconds, minutes, hours, or days. Unit
constructors saturate if multiplication exceeds the i64 tick range. TimeSpan can be negative,
so it can represent a difference that std::time::Duration cannot.
Use checked_add, checked_sub, checked_mul, checked_div, and checked_neg when overflow is
possible. The operator forms panic on overflow or division by zero. Saturating addition,
subtraction, and negation are also available.
Conversions to std::time::Duration reject negative spans. Conversion in either direction has
100-nanosecond precision, so finer source precision is truncated.
`DateTime` points
DateTime stores ticks since 1601-01-01 00:00:00 UTC. Use from_unix_secs orfrom_unix_millis for Unix timestamps, and unix_secs, unix_millis, or unix_nanos for the
reverse direction. DateTime::now() and SystemTime conversions require the default std
feature.
Add or subtract a TimeSpan to move along the timeline. Subtract two DateTime values to obtain a
signed TimeSpan. Prefer the checked methods for values from external input.
Display emits an ISO-8601 UTC timestamp. to_local() shifts the stored ticks so the component
methods return local calendar fields. Treat that result as a local-field carrier; it is not a new
UTC instant to serialize with Display or convert back to SystemTime.
Platform constraints and pitfalls
- Both types use signed
i64ticks, where one tick is 100 nanoseconds. TimeSpanandDateTimeare ABI values. Do not substituteDurationorSystemTimein a WinRT
signature.- Unit and Unix constructors saturate on overflow. Use explicit range validation when saturation
would hide invalid input. DateTime::now()returnsDateTime::MAXif the system clock cannot be represented.- Disable the default
stdfeature only ifnow,SystemTimeconversion, and the standard error
implementation are not needed. Local-time conversion still calls a Windows API.
Sample and next steps
The time-time-types sample demonstrates duration
construction, checked date arithmetic, Unix timestamps, and formatting.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-time.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/time.txt; the
conversion logic is hand-written.
Why not substitute the type at the projection layer
C++/WinRT replaces TimeSpan with std::chrono::duration> andDateTime with the matching time_point. That works only because those chrono types are a singleint64_t and therefore bit-for-bit ABI-compatible with the WinRT struct.
Rust has no equivalent. core::time::Duration is { u64 seconds, u32 nanos } (96 bits, unsigned) -
it can't represent negative TimeSpan values and isn't ABI-compatible with i64, andstd::time::SystemTime is opaque and platform-defined. So the ABI struct stays a single i64, andwindows-time provides explicit conversions to and from the std types instead of aliasing them.
Testing
Run cargo test -p windows-time; see also the workspace test crates.
Crates Windows Version (`docs/crates/windows-version.md`)
windows-version
Reliable OS version information without an application manifest.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 📁 Source
windows-version reports the real operating system version even when the process has no version
manifest.
When to use
Use this crate when Windows documentation gives an OS build requirement and the program must make a
version decision that is not affected by application-manifest version virtualization. It also
provides a workstation/server distinction and the update build revision (UBR).
Prefer direct capability detection when Windows exposes it: attempt the API, query the interface,
or check the feature itself. Version checks are appropriate when the platform contract is stated
only in terms of a minimum Windows version or build.
Getting started
The crate README contains the dependency declaration and a
minimal comparison. In application code, name the required version and keep the decision next to
the feature it guards:
use windows_version::OsVersion;
const REQUIRED: OsVersion = OsVersion::new(10, 0, 0, 22_000);
fn feature_is_available() -> bool {
OsVersion::current() >= REQUIRED
}This is easier to audit than scattering comparisons against individual fields.
Reading version information
OsVersion::current() returns major, minor, service-pack pack, and build. OsVersion
implements Ord, with comparisons proceeding in that field order. Construct a threshold withOsVersion::new.
is_server() distinguishes Windows Server from workstation releases. revision() reads the UBR
from the registry. Treat the revision separately from OsVersion: it is not part of Ord.
For diagnostics, report both values when the revision matters:
let version = windows_version::OsVersion::current();
let revision = windows_version::revision();
println!(
"{}.{}.{}.{}",
version.major, version.minor, version.build, revision
);Platform constraints and pitfalls
- The crate is Windows-only.
- A version threshold does not guarantee that an optional component, driver, policy, or hardware
capability is present. Query those requirements separately. revision()returns0when the registry query fails as well as when the stored revision is
zero. Do not use it where those cases must be distinguished.- Windows product names are marketing labels, not API versions. Compare the documented numeric
build rather than parsing a product name. - Do not use
is_server()as a proxy for installed roles or individual server capabilities.
Next steps
Find the minimum supported build in the documentation for the Windows API or feature being used.
Keep that requirement in one named constant, and add capability checks for requirements not
captured by the OS build.
The version-version sample prints the current version,
revision, product type, and the result of a named build comparison.
Internal documentation
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-version.
How it's built
src/bindings.rs is generated by tool-bindings from crates/tools/bindings/src/version.txt;OsVersion and the comparison logic are hand-written.
Testing
Run cargo test -p windows-version; see also the workspace test crates.
Crates Windows Webview (`docs/crates/windows-webview.md`)
windows-webview
A safe Rust wrapper around the WebView2 COM APIs for hosting
Microsoft Edge in a window.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
When to use it
Use windows-webview when a Windows desktop application needs to host web content, exchange
messages with JavaScript, or use browser facilities such as profiles, cookies, downloads, and the
Chrome DevTools Protocol. The crate has no default UI framework. Enable system to host WebView2
through windows-window, or reactor to place the WinUI XAML WebView2 control in awindows-reactor view. The features are additive.
The crate wraps a selected WebView2 surface rather than exposing the complete SDK. Use raw WebView2
bindings when an application needs APIs that are not represented here.
UI operation model
Low-level WebView2 creation and script operations complete through callbacks. Starting an
operation returns Result<()>: an error means the operation did not start, while success means its
result may later be passed to the callback on the same UI thread. A callback runs at most once and
may be dropped without running if its native operation or UI apartment ends first.
No operation runs a nested message loop. Win32, WinUI, Reactor, and other UI hosts retain control
of message preprocessing, dispatch, and shutdown. WebViewWindowBuilder::run is a separate
convenience for applications that choose windows-window as their top-level host; it owns the one
application message loop.
This differs from windows-pickers: a Common Item Dialog is a synchronous
native modal operation whose Show method owns its modal loop. Reactor stages such modal work withComponentContext::run_window; it uses ElementRef requests for callback-completed control work.
The basic idea
A low-level WebView host has three main objects:
| Type | Purpose |
|---|---|
Environment |
Owns the browser process and user-data context |
Controller |
Places a browser inside a native parent window |
WebView |
Navigates, runs scripts, exchanges messages, and exposes page events |
WebViewHost |
Owns all three objects while another framework owns the parent |
WebViewWindow |
Adds an owned windows-window parent and lifecycle handling |
WebViewHostBuilder creates the first three as one callback-completed operation. Use the individual
types when an application shares an environment or needs a custom creation sequence.
The Microsoft Edge WebView2 runtime must be installed for native window hosting. Low-level hosts
need a live message loop on a COM single-threaded apartment. Standalone applications initialize
one with windows_core::init_sta(). WebViewWindowBuilder::run handles this for its owned loop.
Host your first page
The system feature provides a complete windows-window host:
use windows_webview::*;
fn main() -> Result<()> {
WebViewWindow::new("WebView2")
.size(1000, 700)
.run(|host| {
host.webview()
.navigate("https://learn.microsoft.com/windows/apps/")?;
Ok(())
})
}The host resizes the controller with its parent and closes WebView2 before the HWND is destroyed.
Call host.retain or host.retain_all for event registrations that should remain active for the
host lifetime. WebViewWindowBuilder::on_close reports normal close processing, including
programmatic closure, when another UI framework coordinates several windows. Direct destruction
during drop does not call it.
Use WebViewHostBuilder when a framework already owns the parent HWND and UI loop:
WebViewHost::builder().create_for_hwnd(parent, move |result| {
match result {
Ok(host) => framework.attach_webview(host),
Err(error) => framework.report_webview_error(error),
}
})?;The builder chains environment and controller creation without blocking or pumping messages.WebViewHost owns the resulting environment, controller, browser, and retained event
registrations. The framework stores that one value and remains responsible for parent lifetime,
layout, position notifications, and shutdown. The raw Environment and Controller callback APIs
remain available when a framework needs separate environment reuse or custom controller creation.
Keep event registrations alive
Every on_* method returns an EventRegistration. The registration unsubscribes when it is
dropped, so store it for as long as the callback should run:
let navigation = webview.on_navigation_completed(|args| {
println!("navigation succeeded: {}", args.is_success());
})?;
webview.navigate("https://github.com/microsoft/windows-rs")?;
// Keep `navigation` alive while the message loop runs.For several events, a vector is convenient:
let registrations = vec![
webview.on_document_title_changed(|title| {
println!("the title changed to {title}");
})?,
webview.on_process_failed(|args| {
eprintln!("browser process failed: {:?}", args.kind());
})?,
];Callbacks run on the UI thread. Keep them short so the window and page remain responsive.
Navigate and observe page state
The common navigation methods are direct:
webview.navigate("https://example.com")?;
webview.reload()?;
webview.go_back()?;
webview.go_forward()?;Use source and document_title after navigation:
let page = webview.clone();
let navigation = webview.on_navigation_completed(move |args| {
if args.is_success() {
println!("{} - {}", page.document_title(), page.source());
}
})?;on_navigation_starting can inspect or cancel a request before it begins:
let starting = webview.on_navigation_starting(|args| {
if !args.uri().starts_with("https://") {
_ = args.set_cancel(true);
}
})?;Keep a process-failure handler in a long-running host. A failed render process can usually be
followed by reload; a failed browser process requires a new WebView.
Exchange messages with the page
Page JavaScript sends a value to Rust with:
window.chrome.webview.postMessage({ action: "save", value: 42 });The host receives it through on_web_message_received:
let messages = webview.on_web_message_received(|args| {
println!("{} sent {}", args.source(), args.web_message_as_json());
})?;Inspect source before trusting messages from content that can navigate. Usetry_web_message_as_string when the protocol accepts only strings.
Rust sends a value in the other direction with:
webview.post_web_message_as_json(r#"{"status":"saved"}"#)?;
webview.post_web_message_as_string("refresh")?;The page receives those values with:
window.chrome.webview.addEventListener("message", event => {
console.log("host sent", event.data);
});Use execute_script when Rust needs to run a specific expression:
webview.execute_script("document.title", |result| {
println!("title as JSON: {result:?}");
})?;Script results are JSON encoded. The callback runs later on the UI thread.
Add script before each document loads
add_script_to_execute_on_document_created installs script before page JavaScript runs and
returns its identifier through a callback:
let page = webview.clone();
webview.add_script_to_execute_on_document_created(
"document.documentElement.dataset.host = 'windows-rs';",
move |result| {
let script = result.unwrap();
println!("registered {}", script.as_str());
page.navigate("https://example.com").unwrap();
},
)?;Save the returned ScriptId if the script may need to be removed later:
webview.remove_script_to_execute_on_document_created(&saved_script)?;Wait for successful registration before navigating when the script must apply to the first
document.
Host local files
Map a folder to a virtual HTTPS host rather than navigating to a file: URL:
let folder = concat!(env!("CARGO_MANIFEST_DIR"), "\\web");
webview.set_virtual_host_name_to_folder_mapping(
"app.example",
folder,
HostResourceAccessKind::Deny,
)?;
webview.navigate("https://app.example/index.html")?;The page can now load scripts, styles, and images relative to that origin. The access kind controls
whether other origins may request the folder's content.
For generated or embedded content, intercept a URL pattern and return a response:
let resources = webview.on_web_resource_requested("https://app.example/*", |request| {
if request.uri().ends_with("/style.css") {
Some(
WebResourceResponse::new("body { font-family: Segoe UI, sans-serif; }")
.content_type("text/css"),
)
} else {
None
}
})?;Returning None lets WebView2 continue the request normally. The callback is synchronous, so
prepare expensive content outside it.
Put a WebView inside Reactor
Enable the reactor feature when the browser should participate in a Reactor layout:
windows-webview = { version = "0.100.0", default-features = false, features = ["reactor"] }webview returns a normal Reactor View and sends the initialized browser through a callback:
use windows_reactor::*;
use windows_webview::{EventRegistration, WebView, webview};
struct Browser {
webview: Option<WebView>,
navigation: Option<EventRegistration>,
}
#[derive(Clone)]
enum Message {
Initialized(WebView),
}
fn update(&mut self, message: Message, _context: &ComponentContext<Self>) {
let Message::Initialized(webview) = message;
self.navigation = webview.on_navigation_completed(|_| {}).ok();
_ = webview.navigate("https://learn.microsoft.com/windows/apps/");
self.webview = Some(webview);
}
fn view(&self, _input: &(), context: &mut ViewContext<Self>) -> View {
webview(context.callback(Message::Initialized))
}These methods belong inside the corresponding Component implementation. Initialization happens
after the XAML control enters a live visual tree, not during Component::create.
The convenience function panics on a native initialization error. Use webview_result when the
component should receive and display that error. A self-contained Reactor app must also deployMicrosoft.Web.WebView2.Core.dll; windows-reactor-setup stages it.
The reactor sample embeds a WinUI WebView2 control in a
Reactor view and exercises navigation, scripts, messaging, DevTools, and events through the sharedWebView API. The reactor-window sample instead
uses a Reactor window as a toolbar for a callback-driven WebViewWindow. It setsquit_on_close(false) so both windows share Reactor's UI thread and message loop, and useson_close to keep the Reactor component synchronized with the external window. This sample also
exercises controller zoom and parent-window lifecycle, which the WinUI WebView2 control does not
expose through supported interop.
What to read next
Run an example with cargo run -p webview-<name>.
| Example | What it shows |
|---|---|
minimal |
Complete WebViewWindow hosting |
reactor |
WebView2 embedded in a Reactor view |
Browse crates/samples/webview for focused browser features and
other hosting models.
Internal documentation
This section is for contributors to windows-webview.
The system feature enables the typed windows-window controller helpers. The reactor feature
depends on windows-reactor and bridges its WinUI WebView2 control to the shared browser API.
Raw HWND controller methods remain available without either host feature.
Binding generation
WebView2 ships C/C++ headers rather than Windows metadata. tool-webview builds the committed
bindings in three stages:
| Stage | Implementation | Output |
|---|---|---|
| Headers -> RDL | windows_clang::clang() |
target/webview/WebView2.rdl |
| RDL -> winmd | windows_rdl::reader() |
target/webview/WebView2.winmd |
| winmd -> Rust | windows_bindgen |
crates/libs/webview/src/bindings.rs |
The tool downloads the pinned Microsoft.Web.WebView2 NuGet package. It parses WebView2.h andWebView2Interop.h as separate inputs because the collector emits declarations owned by each
input, then merges both translation units. It uses Windows.Win32.winmd for referenced Win32
types and targets x86_64-pc-windows-msvc with Microsoft extensions. Regenerate withcargo run -p tool-webview; never edit src/bindings.rs.
Bindings use --flat --minimal and the filter in crates/tools/webview/src/webview.txt. Filter
method names are raw metadata names such as put_Bounds and get_CoreWebView2, not projected
names. Implemented interfaces belong in --implement, without method filters. The --dead-code
option keeps interface methods crate-private. The small Win32 filter supplies COM string allocation
and memory-stream support without a dependency on the full windows crate.
Wrapper implementation
Completion handlers and event adapters in handler.rs use implement_decl!, avoiding thewindows-core proc-macro dependencies. OptionsObject uses the same mechanism to implement the
caller-provided environment options interfaces. Its string getters allocate with the COM task
allocator because WebView2 takes ownership.
All WebView2 completion operations use the same callback contract. Their public methods report
immediate initiation failures and retain one-shot handlers for native completion. No operation
owns or nests the host's message loop.
Event adapters convert COM add/remove tokens into EventRegistration. Resource interception also
removes its request filter when the registration drops. protocol.rs converts response bytes to
an IStream with SHCreateMemStream, keeping streams out of the public API.
string.rs handles borrowed UTF-16 input, borrowed WebView2 callback strings, owned LPWSTR
results that must be freed with CoTaskMemFree, and task-allocator output strings returned by
implemented interfaces.
Reactor bridge
The WinUI XAML control exposes a WinRT CoreWebView2, while this crate wraps the COMICoreWebView2. ICoreWebView2Interop2::GetComICoreWebView2 is the supported bridge; the two
objects do not convert through a plain interface cast.
With the reactor feature, src/reactor.rs requests initialization after the control is loaded,
retains the loaded-event and asynchronous-operation state through Reactor, crosses the interop
bridge, and reports one result. The canonical WinRT metadata iswinmd/Microsoft.Web.WebView2.Core.winmd. Reactor setup supplies the matching projection DLL for
self-contained deployment.
Maintenance
Generated bindings carry casing lint expectations and allow dead code only when the Reactor bridge
is disabled. Keep those expectations synchronized with generator output.
test-webview runs the raw host and browser feature fixtures against a live WebView2 runtime.
The self-contained test-reactor-integration WebView fixture covers WinUI control initialization
and the COM bridge. Both packages run with real windows and message pumps.test-webview --headless suppresses its interactive UI; the Reactor integration fixture manages
and closes its own test windows.
Crates Windows Window (`docs/crates/windows-window.md`)
windows-window
Minimal window creation and message-loop support for content hosted by a raw
HWND.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
- Reactor application and notification icons
When to use it
Use windows-window when a Windows desktop application needs a small top-level window and message
loop to host a swap chain, WebView2 controller, Direct2D or Direct3D renderer, or another API that
accepts an HWND. It avoids depending on the full windows crate or generating
application-specific bindings for basic windowing.
The crate is not a general UI toolkit. Menus, controls, input policy, multi-window coordination,
and specialized message handling remain the application's responsibility.
Prerequisites
The crate targets Windows desktop applications. Create and drive a window on the thread that owns
its message queue. Interop code receiving Window::hwnd() must not retain the handle beyond theWindow lifetime.
Window creation attempts to set process DPI awareness to per-monitor v2. Set any different process
DPI policy before creating a Window.
The README contains dependency setup and the minimal create-and-run example.
First workflow: host resize-sensitive content
Most integrations need the following sequence:
- Put shared renderer or controller state behind
Rc<RefCell<_>>or another UI-thread owner. - Build the window with
on_resizeandon_closeclosures that update and release the hosted
content. - Call
create, then useclient_sizefor the initial content size. - Pass
hwndto the hosting API. - Choose
runfor an event-driven host orrun_withfor a render loop. - Drop hosted resources before the
Windowwhen their API requires the parent handle to remain
valid.
on_resize receives client-area width and height in physical pixels. The callback also handles the
initial resize messages that arrive after the builder installs its state.
Window creation and ownership
Window::new(title) returns a WindowBuilder. size sets the initial outer window size, whileclient_size sets the size excluding non-client borders. style and ex_style replace the
defaults with raw WS_* and WS_EX_* values. The defaults are WS_OVERLAPPEDWINDOW and no
extended style. no_redirection_bitmap adds WS_EX_NOREDIRECTIONBITMAP for content supplied by
composition. visible(false) creates a hidden top-level window that can receive messages for
integrations such as notification-area icons.
on_message receives (hwnd, message, wparam, lparam) and returns Option. ReturnSome(result) only when the application fully handled the message. Return None to use the
crate's built-in handling and DefWindowProcW. on_resize and on_move are focused alternatives
for WM_SIZE and WM_MOVE. If on_message handles either message, its focused callback does not
run.
on_close runs before default WM_CLOSE processing destroys the window. Use it to close or drop
hosted resources whose APIs require a live parent HWND. If on_message handles WM_CLOSE,on_close does not run.
quit_on_close controls whether closing the window posts WM_QUIT. It defaults to true for
single-window applications. Dropping a window does not post WM_QUIT, so cleanup and failed
creation cannot terminate an application-owned loop. Set the option to false when window lifetime
and application lifetime differ, then call quit explicitly when the application should exit.
create registers the shared window class, creates the window, shows it when configured as
visible, and returns an error if creation fails. After native destruction, Window::hwnd returns
null and Window::client_size returns (0, 0). Window::close sends WM_CLOSE through the
configured close and quit behavior. Dropping a live Window calls DestroyWindow directly
without posting WM_QUIT.
Choosing a message loop
| API | Use it when | Behavior |
|---|---|---|
run() |
Event-driven updates. | Blocks in GetMessageW until quit. |
run_with(render) |
Consecutive frames may be needed. | Drains messages, then calls render. |
pump() |
An external operation owns the wait. | Dispatches pending messages; never blocks. |
quit() |
Application state requires loop termination. | Posts WM_QUIT to the current thread. |
The run_with closure returns Result. Return Ok(true) to request another immediate frame,
or Ok(false) to block until a message arrives. Propagating an error exits the loop. This lets a
renderer switch between animation and an idle or occluded state without busy-waiting.
pump returns false after consuming WM_QUIT; the caller should then stop its outer loop.
Repeatedly calling pump without another wait mechanism spins the CPU.
Messages, reentrancy, and panics
Message dispatch is reentrant: a handler can call a Win32 API that sends another message before the
first callback returns. The crate temporarily removes user handlers while one runs. Nested messages
therefore use default processing instead of re-entering a closure or borrowing its capturedRefCell again. A nested WM_CLOSE is deferred until the active handler returns so hosted
resources can shut down before the native window is destroyed.
This also means a nested WM_SIZE triggered inside a handler does not invoke on_resize. Apply any
state update needed by that synchronous operation directly.
Handlers run across an extern "system" window-procedure boundary without catch_unwind. A panic
that escapes a handler aborts the process rather than unwinding through Win32. Return errors
through captured application state or catch a panic inside the closure if recovery is required.
Do not perform long blocking work in a message handler. It prevents painting, input, timers, and
other components on the same UI thread from progressing.
Interop and common options
- Use
hwnd()only with APIs that accept a borrowed parent or target handle. - Use
client_size()after creation to size the initial swap chain or child content. - Forward resize callbacks to
Controller::set_boundsforwindows-webview, or resize the relevant swap-chain buffers. - A composition host can use
no_redirection_bitmap()to avoid allocating an unused DWM
redirection surface. - Raw input, paint, keyboard, mouse, DPI, and position behavior can be implemented through
on_message. This crate intentionally does not project message-specific argument types.
Samples
| Sample | What to study |
|---|---|
window-message |
Creation and message handling. |
standalone |
Swap-chain hosting and resize flow. |
Internal documentation
This section is for contributors to windows-window.
src/bindings.rs is generated by tool-bindings fromcrates/tools/bindings/src/window.txt. It contains the minimal flat Win32 surface needed for class
registration, creation, DPI setup, destruction, and message dispatch. The hand-writtenwindow.rs depends only on windows-core.
One class is registered lazily for the process. A boxed state containing optional message, resize,
move, and close handlers and a shared liveness bit is stored in GWLP_USERDATA afterCreateWindowExW. wndproc clears the bit and removes the state on WM_NCDESTROY. Window::drop
destroys only the original native window while it remains live. This prevents a late drop from
acting on an HWND
value that Windows has recycled for another window.
Before invoking a callback, wndproc takes all handlers out of state. After the callback it readsGWLP_USERDATA again because synchronous handling may have destroyed the window and freed the
state. It restores the handlers only when the state still exists. Keep this ordering when changing
dispatch behavior.
The crate does not catch panics in wndproc, add message-specific wrappers, or coordinate several
top-level windows. Those boundaries keep the crate small and its ownership rules explicit.
Crates Windows (`docs/crates/windows.md`)
windows
A broad projected Windows API for Rust binary applications.
- 📦 crates.io
- 📖 API reference
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
The windows crate projects Win32, COM, and WinRT metadata into Rust. It keeps the Windows API
shape while adding typed handles, COM reference counting, string and parameter conversions, andResult-returning wrappers where the metadata describes an HRESULT result.
Choosing a binding crate
| Need | Choose | Why |
|---|---|---|
| Focused crate covers the task | That focused crate | Small, stable dependency and safer API |
| Reusable library needs another API | windows-bindgen |
Owned binding set |
| Binary application needs broad rich bindings | windows |
Pre-generated projected API |
| Binary application needs broad raw bindings | windows-sys |
Pre-generated FFI |
Start with the focused crates listed in the crate index. They provide smaller
dependencies and APIs designed around a particular task. If a reusable library needs APIs they do
not expose, generate a private, reviewed binding set with windows-bindgen.
Use windows when a binary application benefits more from a pre-generated broad projection than
from owning generated bindings. Cargo features limit what compiles, but the umbrella crate still
adds dependency weight and version churn. Different libraries can otherwise bring incompatible
versions of the same large crate into one dependency tree.
It is possible to mix windows and windows-sys, but their similarly named types are distinct.
Keep conversions at a small boundary rather than passing values from both crates throughout an
application.
Finding an API and its features
Windows APIs retain their metadata organization:
- WinRT namespaces use Rust modules such as
windows::Data::Xml::Dom. - Win32 header groups are exposed below
windows::Win32, such ashandleapi,synchapi, andwinuser. - Shared runtime types and traits are under
windows::core.
Use this workflow when adding an API:
- Find the Windows API by its native name in the
API reference. - Read the projected Rust signature rather than translating the C signature by hand.
- Enable the feature shown for the item. WinRT features follow namespace names with
_separators,
such asData_Xml_Dom. Win32 features usually follow lowercase header groups, such ashandleapi. - Import narrowly while learning the API. A path such as
windows::Win32::CreateEventWmakes
missing features and name collisions easier to diagnose than a glob import. - Add supporting features reported by the compiler or reference. Cargo feature dependencies pull
in many prerequisite types automatically, but a function and the types in its signature may be
gated separately.
Keep the feature list near the code that motivates it. For a workspace, a shared dependency can
centralize the version while each package selects only the features it calls. Avoid enabling every
feature as a discovery shortcut: it slows builds and hides the intended API boundary.
If a documented item does not appear after enabling its feature, check the target first. Thewindows crate is Windows-only, and availability in Windows metadata does not guarantee that an
API exists on every supported Windows version. Runtime version checks and documented fallback
behavior remain the application's responsibility.
Reading projected signatures
The Rust signature is the contract. Projection changes common ABI details in useful ways:
- Nullable pointers may become
Option. - C
BOOLinputs may become Rustbool. - String and interface inputs may accept
Param<T>implementations rather than one exact type. - COM and WinRT out parameters may become return values.
- HRESULT-returning methods may become
windows::core::Result<T>. - Slice-shaped pointer and length pairs may become Rust slices.
These changes do not make every Windows call safe. Generated functions remain unsafe when the
caller must uphold pointer, lifetime, threading, initialization, or API-specific invariants. Keep
the unsafe block small and document the invariant in the safe wrapper around it.
Ownership and types
COM and WinRT interface values are owning, reference-counted wrappers. Cloning one calls AddRef,
dropping one calls Release, and Interface::cast performs QueryInterface. Prefer those
operations over manipulating raw interface pointers.
Win32 handle types such as HANDLE and HWND improve type checking but do not imply ownership.
The API documentation determines whether a returned handle is borrowed, owned, or a sentinel and
which function releases it. Pair acquisition and release in one abstraction so early returns do
not leak the resource.
Generated structs follow the Windows ABI. Initialize them with Default when supported, then set
required size, version, discriminant, or pointer fields exactly as the native API requires.
Constants sometimes need a cast because the metadata preserves the native constant type while a
function accepts another integer type.
Errors
windows::core::Result<T> and windows::core::Error cover HRESULT-based failures. The ? operator
is the normal path for projected methods that return Result.
Win32 APIs use several other failure conventions. A function may return BOOL, a null handle,INVALID_HANDLE_VALUE, zero, or another sentinel. Do not apply one check to every function:
- Use
.ok()?when a projectedBOOLorHRESULTis documented as success/failure. - Check handle and integer sentinels exactly as documented.
- Call
Error::from_thread()immediately after an API reports a last-error failure. Another
Windows call may overwrite the thread's last-error value. - Do not treat every zero as failure. Some APIs use zero as a valid result or require a separate
status query.
The getting-started example demonstrates both patterns: CreateEventW requires a null-handle
check, while SetEvent and CloseHandle return values support .ok()?.
Strings
Choose a string representation from the parameter type and ownership contract:
| Form | Use |
|---|---|
w!("text") |
Static null-terminated UTF-16 input (PCWSTR) |
s!("text") |
Static null-terminated narrow input (PCSTR) |
h!("text") |
Static WinRT HSTRING |
HSTRING |
Owned WinRT string, including dynamic text |
BSTR |
Owned Automation string |
PCWSTR / PCSTR |
Borrowed const pointer with externally managed lifetime |
PWSTR / PSTR |
Mutable pointer, commonly for caller-provided output storage |
Prefer the W variant of a Win32 API for new code. The A variant uses an API-specific Windows
code page, not Rust UTF-8. The literal macros include the required trailing null where applicable.
For dynamic pointer strings, keep the backing buffer alive and immovable for the entire call and
add a trailing null yourself. A pointer string has no ownership; never free it unless the API says
the caller owns the allocation.
A practical wrapper workflow
When a binary application wraps an API from this crate:
- Keep generated types and calls in a Windows-specific module.
- Validate Rust inputs before entering
unsafe. - Convert strings and buffers once, immediately before the call.
- Check the API's documented success convention before making another Windows call.
- Move owned handles or interfaces into a type whose
Dropimplementation uses the matching
release function. - Return ordinary Rust values and
Resultfrom the public wrapper.
This boundary makes API version checks, resource cleanup, and platform-specific tests visible. It
also prevents raw pointers and similarly named windows and windows-sys types from spreading
through the rest of the program. A reusable library should apply the same boundary to focused
crates and private windows-bindgen output instead of depending on this umbrella crate.
Internal documentation
The remainder of this page is for repository contributors.
How it is built
The published crate is generated by tool-package, which drives windows-bindgen in --package
mode from crates/tools/package/src/windows.txt. It emits src/Windows/mod.rs and one file per
metadata namespace. src/lib.rs is hand-maintained and supplies the docs.rs stub that redirects to
the external API reference.
The generated Cargo features mirror namespaces and Win32 header groups. Feature dependency edges
are generated from type and namespace dependencies. The crate re-exports windows-core aswindows::core and depends on the focused collections, future, numerics, reference, and time
crates used by the projection.
Do not edit generated source under crates/libs/windows/src/Windows. Change metadata, filters, or
the generator and run the appropriate repository tool.
Testing
Run cargo test -p windows; the workspace test crates and Windows samples cover broader API and
projection behavior.
--- METRICS ---
- Files Extracted: 88
- Estimated Token Budget: ~86934 tokens
- Canonical Reference: https://codewiki.google/github.com/microsoft/windows-rs