Minimal window creation and message-loop support for hosting graphics and other content against a raw
HWND.
- Not published to crates.io
- Getting started
- Source
windows-window provides just enough Win32 windowing to open a top-level window and run a message
loop, so crates and samples that need a host window - windows-canvas swap
chains, WebView2 controllers, Direct2D/Direct3D rendering - don't have to depend on the full
windows crate or hand-roll a windows-bindgen build script.
It is intentionally small: a Window with an optional message handler and resize callback, plus
run, run_with, and quit. All Win32 details are private; you work with a safe Window and the
raw HWND it exposes for interop.
use windows_window::*;
let window = Window::new("Hello")
.size(800, 600)
.on_resize(|width, height| {
// forward the new client size to your renderer
})
.create()?;
// Hand window.hwnd() to windows-canvas, WebView2, Direct2D, etc.
let hwnd = window.hwnd();
run(); // blocking, event-driven message loopWindow::new(title)returns aWindowBuilder. Configure it with.size(width, height),.style(..),.ex_style(..),.on_message(..), and.on_resize(..), then.create()to open and show the window.Window::hwnd()returns the raw*mut c_voidhandle for interop.Window::client_size()returns the current client area as(width, height).style(WS_*)/ex_style(WS_EX_*)override the window styles (rawu32). They default toWS_OVERLAPPEDWINDOWand none - pass Win32 style constants for custom chrome (e.g.WS_EX_NOREDIRECTIONBITMAPfor a DirectComposition target).on_message(|hwnd, message, wparam, lparam| -> Option<isize>)handles raw window messages; returnSome(result)to handle a message orNoneto fall through to default processing.on_resize(|width, height|)is a convenience forWM_SIZE, giving the new client size in pixels.
Handlers are detached while they run, so a handler that triggers reentrant dispatch (for example
calling SetWindowPos, which synchronously sends WM_WINDOWPOSCHANGING / WM_SIZE) won't re-enter
itself - the nested messages fall through to default processing. This keeps shared state behind an
Rc<RefCell<..>> safe from reentrant borrows.
wndproc invokes your handlers directly, without a catch_unwind, so a panic that escapes a
handler aborts the process at the extern "system" boundary rather than unwinding into the Win32
frames that called it - much like a C++ noexcept function terminating. This is intentional; the
crate does not try to recover from handler panics. If a handler needs to survive its own panics,
wrap its body in std::panic::catch_unwind (usually via AssertUnwindSafe, since handler closures
capture mutable state).
run()- a blocking, event-driven loop (GetMessage) until the window closes. Best for interactive apps such as a WebView2 host.run_with(render)- callsrenderwhenever the queue is empty;renderreturnsOk(true)to keep rendering immediately (continuous animation) orOk(false)to wait for the next message (event-driven/occluded). ReturnsOk(())when the window closes, or early ifrenderreturns an error.quit()- posts a quit message. An unhandledWM_DESTROYposts one automatically, so closing the window ends the loop without extra code.pump()- dispatches all currently-pending messages without blocking and returnsfalseif a quit was received. Lets a caller drive the loop while waiting on an external condition, such as pumping until an asynchronous callback completes.
create_windowthe minimal case - open a window and pump messages withrun_with, depending only onwindows-window(nowindowscrate).window_messagehandles raw messages (WM_PAINT, mouse, keyboard) viaon_message, with direct access towparam/lparam.canvas/standalonehosts awindows-canvasswap chain in awindows-windowwindow driven byrun_with.windows/direct2ddrives a Direct2D swap chain withrun_with, rendering only while visible.windows/direct3d12binds a Direct3D 12 swap chain towindow.hwnd()and renders onWM_PAINT.windows/dcomphosts a DirectComposition target (customex_style) and pumps withrun, relying on reentrancy-safe handlers for its DPI handling.
The remainder of this page covers how the crate is built and maintained. It is for contributors and
is not needed to use windows-window.
src/bindings.rs is generated by tool_bindings from crates/tools/bindings/src/window.txt -
minimal, flat bindings for the RegisterClassW / CreateWindowExW / message-pump surface plus the
handful of structs and constants they need. Everything in window.rs (the Window,
WindowBuilder, and the message loop) is hand-written over those bindings and depends only on
windows-core, so the crate stays fast to build.
- One window class - a single process-wide window class is registered lazily; all windows share
it and route messages through one
wndproc. - State in
GWLP_USERDATA- the boxed message/resize handlers are stored in the window's user data, set afterCreateWindowExWreturns, and reclaimed onWM_NCDESTROY.Window::dropcallsDestroyWindowif the window still exists. - Reentrancy-safe -
wndprocmoves the handlers out of the state before invoking them and restores them afterward, so a handler that pumps nested messages can't alias (or, behindRefCell, panic on re-borrowing) itself. - No
catch_unwind- handlers are invoked directly, so a panic aborts at theextern "system"boundary (see The API above). This is deliberate: recovering from handler panics is out of scope for this crate. - Not a kitchen sink - the crate covers window creation, a resize hook, and a message loop only.
Anything beyond that (menus, input, multiple monitors, etc.) belongs in the consuming app or a
focused
windows-bindgenbinding.
cargo check -p windows-window and cargo clippy -p windows-window --all-targets. The
canvas/standalone sample exercises it end to end.