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.
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. - 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.