The code generator that turns Windows metadata (
.winmd) into Rust bindings.
- 📦 crates.io
- 📖 docs.rs
- 🚀 Getting started
- 🧩 Samples
- 📁 Source
windows-bindgen generates Rust bindings from Windows metadata. It powers the windows and
windows-sys crates. You can also use it from build.rs to make a small binding set for the APIs
that your crate calls.
The crate includes the standard Windows metadata through
windows-default. Most projects only need an output file and a filter. If an
API has no metadata, use windows-rdl to create a .winmd file. Then pass that
file to windows-bindgen.
Add windows-bindgen as a build dependency. Add windows-link or windows-core as the runtime
dependency that the generated code uses:
[dependencies]
windows-link = "0.100"
[build-dependencies]
windows-bindgen = "0.100"Generate bindings from build.rs with command-line-style arguments, a command file, or the builder:
windows_bindgen::bindgen([
"--out", "src/bindings.rs",
"--flat",
"--sys",
"--filter", "GetTickCount",
]);windows_bindgen::Bindgen::new()
.output("src/bindings.rs")
.flat()
.sys()
.filter("GetTickCount")
.write();Include the generated file as a module in your crate.
A filter selects which APIs appear in the output. Rule specificity controls how much of a type is
generated. This works like a Rust use declaration. Use a bare name for the full item. Use braces
to select a smaller surface.
- A namespace, such as
Windows.Win32.System.Com, includes all types under it. - A bare type includes the full type. Examples are
HWND,OSVERSIONINFOEXW, andWindows.Win32.Foundation.HWND. Namespace.Type::{}emits a name-only shell. Use it for a dependency that you only pass through signatures.Namespace.Type::{Method1, Method2}emits only the named methods.Namespace.Type::Methodis the single-method form.PropertyandEventnames expand to accessor pairs. Properties expand toget_andput_. Events expand toadd_andremove_.Namespace.Class::CreateInstanceemits class activation support. A bare class projects its default interface but no constructor.
Prefix a rule with ! to exclude it. A selected type also pulls in the types that its signatures
require. Those dependency types are emitted as shells.
For a complete command file, use --etc. Blank lines and lines whose first non-whitespace
characters are // are ignored:
--out crates/libs/version/src/bindings.rs
--flat --sys
--filter
RtlGetVersion
OSVERSIONINFOEXW
VER_NT_WORKSTATION
windows_bindgen::bindgen(["--etc", "bindings.txt"]);When only the filter list is large, keep it in a filter-only file and use
Bindgen::filter_file/filter_files or the textual --filter-file option.
The in-repo crates use both patterns. tool_bindings runs
bindgen(["--etc", "crates/tools/bindings/src/<crate>.txt"]) for each library.
Two independent choices control the generated code. The first choice is style. The second choice is layout.
Style:
- Default style emits rich bindings. It includes class wrappers, inherited-interface forwarders,
handle types, and free-function wrappers. The
windowscrate uses this style. --sysor.sys()emits raw FFI. It emits bareexternfunctions and plain structs. It links throughlink!macros. Add--externor.extern_fns()to emitextern { fn ... }blocks instead oflink!. Thewindows-syscrate uses this style.--minimalor.minimal()starts from default style. It omits per-class wrappers, inherited forwarders, handle helpers, and free-function wrappers. Use it for small binding sets.windows-canvasandwindows-reactoruse it. It is mutually exclusive with--sys.
WinRT event accessors are always collapsed into an Event wrapper. This applies to all styles and
layouts. See Event accessors.
Native variadic exports carry MethodCallAttributes::VARARG in the method signature. Only
--sys emits them, because sys output can retain the literal ... tail in a raw foreign
declaration. Both link! output and --sys --extern preserve metadata C and system calling
conventions. Rust lowers a Windows system C-variadic declaration to the compatible C variadic ABI
on X86 while retaining system for fixed signatures.
Default and minimal output cannot forward an unknown variadic tail through a Rust wrapper. Broad
filters omit those exports. Selecting one by exact function name reports that rich and minimal
bindings cannot project it and directs the caller to --sys; it never emits the fixed prefix as a
callable function. Stable Rust cannot declare a fastcall C-variadic function, so broad sys
generation omits that metadata shape and exact selection reports the unsupported convention.
The published windows-sys crate retains raw declarations such as
AuthzReportSecurityEvent(...). The windows crate omits Win32 and WDK variadic exports because
the default projection cannot preserve their argument tails.
Layout:
- The default layout emits one Rust module per metadata namespace.
--flator.flat()emits one flat list of items.--packageor.package()emits one file per namespace. It also writes aCargo.tomlwith per-namespace features. Thewindowsandwindows-syscrates use this layout. It is mutually exclusive with--flat.
The style and layout choices are independent. The repository uses only the combinations below.
| Style + layout | Purpose | Examples |
|---|---|---|
default + --flat |
Helper crate with one bindings file | windows-collections, windows-future |
default + --package |
Published umbrella crate | windows |
--sys + --flat |
Raw FFI helper crate with one bindings file | windows-result, windows-registry |
--sys + --package |
Published raw FFI crate | windows-sys |
--minimal + --flat |
Small binding set | windows-core, windows-canvas, windows-reactor |
| any + modules | Namespace-per-module output for direct consumers | External binding generation |
--minimal and --package are not used together. Minimal output targets small binding sets.
Package output targets the full API surface.
In --sys --package mode, a namespace can contain only COM interfaces. Raw FFI style emits no
interface bodies, so the namespace has no items. write_package prunes that empty namespace. It
removes the module declaration, file, Cargo feature, and feature dependency references.
Pruning is recursive. A parent namespace is pruned only when it and all children are empty. This
applies only to --sys. The full windows crate emits interfaces, so those modules are not empty.
Each WinRT add_X and remove_X pair becomes one method:
Event add methods return Result<EventRevoker>.
The method takes the closure directly. It returns an
EventRevoker. The
revoker calls the matching remove_X slot on drop. Call .forget() or .into_token() to opt out.
This rule changes only the consumer side. Implementing an event source still requires both add_X
and remove_X.
--in,.input(..), and.inputs(..)add.winmdfiles or directories. The builder uses the standard metadata implicitly when no input is supplied. Builder inputs accept strings,Path, orPathBuf. Use.input_default()to combine the bundled metadata with custom inputs; the textual--in defaultform provides the same behavior..output(..)accepts a string,Path, orPathBuf.--deriveand.derive(..)add derives to generated types.- Bare
--implementand.implement_all()emit_Implscaffolding for every WinRT interface in scope. Use.implement(name)or.implements(names)to limit scaffolding to type names or namespace prefixes. --rustfmtand.rustfmt(..)set the formatter for the output.--dead-codeand.dead_code()emitpub(crate)for callable items. This lets the compiler flag unused generated callables.
A build.rs can regenerate bindings on each build. Published crates usually use a different
pattern. Commit src/bindings.rs as source. Depend only on windows-link at
runtime. Consumers then build without code generation, metadata files, or a windows-bindgen
dependency.
The pattern has three parts.
1. The published crate depends only on windows-link and includes the committed bindings:
# tickcount/Cargo.toml
[dependencies]
windows-link = "0.100"The library module includes the generated bindings and exposes the safe API used by the binary.
2. A separate, unpublished binary owns code generation. Keep it as a workspace member. It does not become a dependency of the published crate:
# gen/Cargo.toml
[package]
name = "gen"
publish = false
[dependencies]
windows-bindgen = "0.100"// gen/src/main.rs
windows_bindgen::bindgen([
"--out", "tickcount/src/bindings.rs",
"--flat",
"--sys",
"--filter", "GetTickCount64",
]);--out is resolved relative to the current directory. Run the tool from the workspace root:
cargo run -p gen3. A CI check keeps the committed bindings current. Regenerate, then fail if the result differs from the checked-in file:
- run: cargo run -p gen
- run: git diff --exit-codeThis repository uses the same arrangement.
tool_bindings
regenerates each crate's bindings.rs from a .txt filter. The
gen.yml workflow
runs the tools and rejects any diff.
The published windows crate projects public, documented APIs behind Cargo features. Some consumers
need a smaller slice or an API that is not in public metadata.
Use windows-bindgen for these cases instead of expanding the windows crate.
- If the API is public but belongs to a broad feature, generate a small binding set with a filter.
For example, a crate can select
IPropertyStoreandPROPVARIANTwithout enabling the full feature surface that contains them. - If the API is not in public metadata, author metadata with
windows-rdl. Then feed that metadata towindows-bindgen. This keeps the FFI surface generated and typed.
The rest of this page covers how the crate is built and maintained. It is for contributors and is
not needed to use windows-bindgen.
windows-bindgen is hand-written. It is the generator that other crates use. It reads ECMA-335
metadata through windows-metadata. The bundled metadata inputs live in
the windows-default crate.
Two tools drive it in this repository:
tool_bindingsreads the per-crate.txtfilters incrates/tools/bindings/src.tool_packageproduces the publishedwindowsandwindows-syscrates.
The generator models output style as named policies. These policies keep style checks in one place and make call sites describe intent.
Style::emit_class_methodsemits per-class wrapper methods.Style::emit_inherited_forwardersemits inherited-interface forwarders.Style::emit_iterable_into_iteratoremits theIntoIteratorbridge for an inheritedIIterable<T>.Style::minimal_string_inputandStyle::minimal_string_returnexposeHSTRINGparameters and returns as&strandString.Config::emit_runtime_nameemits the WinRTNAMEruntime-name constant.Style::derive_std_traitsemitsDefault,Debug, andPartialEqderives.Style::emit_core_traitsemits thewindows-coretrait block.Style::emit_bare_typedefemits handle structs and unscoped enums as type aliases.
--dead-code visibility is centralized in Config::item_vis(). It is used for callables such as
methods and delegate constructors. Nameable public items stay pub, because hand-written crates can
re-export them or reference them from exported macros.
Repeated layout helpers also live on Config:
Config::doc_hidden_in_packageemits#[doc(hidden)]in package mode.Config::write_value_name_constwrites theRuntimeType::NAMEconstant for value types.
For precise filters, TypeClosure::build starts from the selected types and walks signature
dependencies. It emits selected entry points as full types. It emits dependency types as shells
unless they are selected directly.
For broad filters and package generation, TypeMap::filter scans namespaces from the top down. This
is used for full namespace and package output.
The --minimal flag affects rendering only. It does not change which referenced types are included.
The metadata reader classifies types as WinRT or Win32/COM from the metadata type attributes. Shared code handles names, signatures, dependencies, and type remapping. The writers stay separate where the ABI rules differ.
The main differences are:
- WinRT vtable methods return
HRESULT. The projection wraps them inResult. - COM methods keep their native return shape.
ReturnHintcontrols the projected shape for common COM patterns. - WinRT supports generics, runtime signatures, activation, and
RuntimeType. - WinRT delegates are COM interfaces with
Invoke. COM callback types can be raw function pointers. - Win32 also has free exports, constants, handles, unions, nested types, and architecture-specific layout.
Some writer pieces are shared. Interface vtable method fields and _Impl method iteration use
common helpers. Enum constant and flag operator emission also use common helpers.
Win32 structs frequently pack several logical members into one storage unit with C bit-fields:
typedef struct _MIB_IF_ROW2 {
// ...
struct {
BOOLEAN HardwareInterface : 1;
BOOLEAN FilterInterface : 1;
BOOLEAN ConnectorPresent : 1;
// ...
} InterfaceAndOperStatusFlags;
} MIB_IF_ROW2;The winmd format has no bit-field concept. The scrape coalesces each run of bit-fields into one
backing integer field named _bitfield. If a struct has more runs, the fields are named
_bitfield1, _bitfield2, and more. The backing field is emitted as public FFI data:
windows-bindgen also generates a typed getter and setter for each logical member. The data comes
from NativeBitfieldAttribute metadata on the backing field. The accessors are generated for
non-sys styles.
Generated accessors read and update each member without manual bit arithmetic.
A width-1 member projects as bool. Wider members project as the backing integer type. Reads shift
through the backing type so signed backing fields sign-extend and unsigned backing fields
zero-extend. Writes clear the target bits and OR in the masked value. Identity shifts are omitted so
generated code stays clean under -D warnings.
The RDL spelling is a C-like bit-field block on the backing field:
_bitfield: u8 { HardwareInterface: 1, ... }
See windows-rdl for RDL input. Test coverage lives in
crates/tests/libs/clang/input/bitfields.h and
crates/tests/libs/bindgen/input/struct_bitfield.rdl.
NativeArrayInfoAttribute and MemorySizeAttribute can replace an input or input/output
pointer/count pair with a slice parameter in rich output.
windows-metadata::reader::MethodParam::buffer_relationship decodes the literal signed
relationship:
CountParamIndexidentifies an element-count parameter.BytesParamIndexidentifies a byte-count parameter.CountConstsupplies a fixed element count.
The metadata reader checks property names and value types. Invalid or conflicting relationships
return None, which keeps the raw ABI shape. It does not interpret parameter positions, pointer
shapes, or public projection policy.
Buffers marked Output without Input keep their pointer and count parameters so callers may
supply uninitialized storage. For other buffers, before CppMethod indexes a related parameter,
it rejects negative, out-of-range, and self-relative indexes and verifies that the count is one
input scalar used by one buffer. Byte counts still require byte-sized elements. A fixed
CountConst must be nonnegative and fit the maximum Rust object size on 32-bit Windows. If any
check fails, generation keeps the pointer and count parameters exactly as the ABI declares them
and adds no slice or array sugar.
windows-metadata::reader::MethodParam supplies the raw direction, optional, reserved, and retval
facts. Bindgen's local Param::is_input_only then applies Rust policy: Input and Unspecified
are input-only, while Output and InputOutput take the output-capable branch. An eligible In+Out
counted buffer becomes &mut [T]; treating the presence of In as input-only would incorrectly
make writable storage const. An output-only counted buffer remains *mut T plus its count because
&mut [T] would require initialized values before the call.
Bindgen also keeps its optional-or-reserved Option shaping local. Metadata does not combine those
facts because they are projection policy rather than metadata structure.
A trailing parameter becomes a projected return only when it is an output-only, required,
non-reserved, uncounted pointer. An explicit RetValAttribute bypasses the heuristic requirements
that every preceding parameter be input-only, that the pointee is not void, and that it fit the
existing 128-bit size limit; it does not bypass the other candidate checks. Without that attribute,
any preceding Output or InputOutput parameter keeps the trailing pointer in the parameter list.
Dedicated test crates cover the generator and related metadata tools: test_bindgen, test_rdl,
and test_clang. variadic_fn* covers rich, minimal, sys-link, sys-extern, C, system, and
unsupported fastcall output. buffer_relationships covers valid, negative, out-of-range,
self-relative, byte-counted, and fixed-count metadata. The interface_out_array golden pins raw
output buffers and In+Out mutable slices. method_params pins In+Out mutable projection, and
method_return covers explicit and heuristic retval selection with In+Out, optional, reserved, and
counted exclusions plus explicit void-pointer and large-pointee returns.