Skip to content

Latest commit

 

History

History
280 lines (225 loc) · 11.4 KB

File metadata and controls

280 lines (225 loc) · 11.4 KB

windows-rdl

A parser for RDL (Rust Definition Language) and an ECMA-335 metadata generator.

windows-rdl is the front of the metadata-authoring pipeline. It parses RDL (Rust Definition Language) — a compact, Rust-like syntax for describing Windows APIs — and compiles it into the same ECMA-335 .winmd metadata that windows-bindgen consumes. It also runs the pipeline in reverse (.winmd → canonical RDL). When an API ships only as a header and has no metadata of its own, the companion windows-clang crate scrapes the C/C++ headers into RDL that this crate then compiles.

Where windows-bindgen answers "I have metadata, generate Rust", windows-rdl answers "I don't have metadata yet — produce some". The two are designed to be used together: windows-rdl manufactures a .winmd, then windows-bindgen turns it into bindings.rs.

Getting started

Add windows-rdl as a build dependency (it is typically run from a small codegen tool or build.rs, not shipped at runtime):

[build-dependencies]
windows-rdl = "0.3"

The crate exposes three builders, one per direction of the pipeline:

  • reader() — RDL source → .winmd metadata.
  • writer().winmd metadata → canonical RDL source.
  • clang() — C/C++ headers → RDL source.

RDL → winmd, and back

Use the reader to compile .rdl into a .winmd, and the writer to regenerate canonical .rdl from a .winmd:

// RDL source -> winmd metadata.
windows_rdl::reader()
    .input("example.rdl")
    .output("example.winmd")
    .write()
    .unwrap();

// winmd metadata -> canonical RDL source.
windows_rdl::writer()
    .input("example.winmd")
    .output("example.rdl")
    .write()
    .unwrap();

RDL can reference types it does not define (for example HRESULT or Windows::Win32::System::Com::IUnknown). Add the standard metadata as an extra reader input so those references resolve — the bundled metadata lives in crates/libs/bindgen/default:

windows_rdl::reader()
    .input("example.rdl")
    .input("crates/libs/bindgen/default")
    .output("example.winmd")
    .write()
    .unwrap();

C/C++ headers → RDL

When an API ships only a C/C++ header, the windows-clang crate's clang() parses it into RDL, which the reader then compiles to metadata. Each header is parsed as its own translation unit — only its own top-level declarations are emitted, not the things it #includes — so list every header you need as a separate input:

windows_clang::clang()
    .args(["-x", "c++", "--target=x86_64-pc-windows-msvc"])
    .input("Example.h")
    .input("crates/libs/bindgen/default/Windows.Win32.winmd")
    .output("example.rdl")
    .namespace("Example")
    .library("Example.dll")
    .write()
    .unwrap();

RDL syntax

RDL looks like a stripped-down Rust module. A top-level mod is a metadata namespace, tagged #[winrt] or #[win32] to choose the type system. Attributes map to metadata attributes, and the item keywords mirror the metadata kinds.

#[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;
    }
}

WinRT types use the #[winrt] namespace flavor and 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 a small, focused .rdl file for each construct (struct_nested.rdl, enum_flags.rdl, delegate.rdl, interface_generic.rdl, union.rdl, and so on) and doubles as a syntax reference.

How it fits with windows-bindgen

windows-rdl and windows-bindgen are complementary halves of one pipeline:

C/C++ headers ──clang()──►  .rdl  ──reader()──►  .winmd  ──bindgen()──►  bindings.rs
 (windows-rdl)                       (windows-rdl)            (windows-bindgen)

When metadata already exists you skip straight to windows-bindgen. You reach for windows-rdl when you need to create the metadata first — either by hand-authoring RDL for types that have no metadata, or by lifting them out of a C/C++ header. Two in-repo tools show both shapes:

  • tool_webview runs the full path. WebView2 ships only a C/C++ header, so clang() produces WebView2.rdl, reader() compiles it to WebView2.winmd, and windows_bindgen::bindgen turns that into the bindings for the windows-webview crate. (crates/tools/webview/src/main.rs.)

  • tool_reactor hand-authors the small set of COM interfaces and bootstrap functions that the WinUI/WinAppSDK metadata omits in crates/tools/reactor/src/extras.rdl, compiles it with reader() alongside the in-house Win32 winmd (crates/libs/bindgen/default/Windows.Win32.winmd) into extras.winmd, and feeds that to windows_bindgen::bindgen together with the standard metadata to generate the windows-reactor bindings. (crates/tools/reactor/src/main.rs.) The reactor filter files (base.txt/test.txt) use the flat Windows::Win32::<Name> namespace.

In both cases the reader is given the standard metadata as an additional input so that references from the authored RDL (Win32 handles, IUnknown, structs, and the like) resolve against the canonical 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-rdl.

How it's built

The RDL grammar is parsed with syn/quote/proc-macro2, reusing Rust's own tokenizer so the syntax stays Rust-shaped. The reader lowers that syntax tree to ECMA-335 and emits a .winmd through windows-metadata; the writer walks metadata read back through the same crate to regenerate canonical RDL. The clang path uses clang-sys to parse C/C++ translation units and project their declarations into the RDL syntax tree, so the header and reader paths converge on the same lowering code. A formatter module pretty-prints generated RDL.

Testing

Verified by the dedicated test crates test_rdl (RDL ↔ winmd round-trips, with the input/*.rdl fixtures) and test_clang (header → RDL goldens under expected/*.rdl), plus the rdl_roundtrip tool. Downstream, test_bindgen covers the .winmd → Rust step that consumes this crate's output. Run cargo test -p test_rdl and cargo test -p test_clang.

The in-house Windows.Win32.winmd

Beyond compiling authored RDL, windows-rdl is the reader stage of an in-repo pipeline that builds a faithful, in-house Windows.Win32.winmd directly from the Windows SDK headers, replacing the reference Windows.Win32.winmd from win32metadata for the hand-authored library crates. The windows-clang scraper (tool_win32) reads the SDK headers, SAL annotations, and import libs and commits a browsable RDL snapshot under metadata/win32/; this crate's reader compiles that snapshot into the winmd. The scraper's design — the faithful-metadata principle, header partitioning, the editorial-deviation ledger, and the canonical type remaps — is documented in windows-clang; this section covers the winmd artifact those tools produce.

Every maintained crate that needs Win32 metadata — the minimal-binding library crates and windows-reactor — resolves against the in-house winmd. The only remaining consumers of the reference winmd are the frozen monolithic windows / windows-sys crates and the intentional parity probes.

The winmd layout

  • crates/libs/bindgen/default/Windows.winmdWinRT metadata, rebuilt in-house by tool_windows by merging the per-contract winmds from the Windows SDK Contracts NuGet package (there is no in-house WinRT scraper — the inputs are the SDK's own reference winmds, merged rather than compiled from headers).
  • crates/libs/bindgen/default/Windows.Win32.winmdin-house Win32 metadata, written by tool_win32. This is what windows-bindgen's bundled "default" bindings resolve to.
  • crates/libs/bindgen/default/Windows.Wdk.winmdin-house WDK metadata, written by tool_wdk.
  • crates/tools/package/reference/Windows.Win32.winmd / Windows.Wdk.winmd — the frozen win32metadata reference winmd, and crates/tools/package/reference/Windows.winmd — a frozen snapshot of the in-house WinRT winmd. Together they back the published windows / windows-sys crates and the tool_features page (and the parity probes), pinning those outputs so a live-metadata refresh never churns them.

The committed RDL snapshot (metadata/win32/*.rdl) is the reviewable source of truth — every scrape change is a readable git diff; the merged binary winmd is git-ignored and rebuilt by tool_win32. The build is deterministic: the writer stages tables in BTreeMaps and the module MVID is a fixed zero GUID, so regeneration is byte-for-byte reproducible across platforms.

Multi-arch merge

tool_win32 scrapes each target architecture (x64, arm64, x86) into its own RDL set, then merge_arch_rdl coalesces them into one winmd. A type identical across every arch is emitted once (arch-neutral); a type that diverges is split into per-arch copies tagged #[arch(X86|X64|Arm64)]. The collapse-or-split decision is made structurally by the merge in windows-metadata — see its documentation for the signature that drives it. merge_arch_rdl itself is the orchestration: it reads each arch's RDL, runs the merge, and writes the combined result, using a per-process scratch directory cleaned up on every return path.

Frozen: tool_package and the monolithic crates

tool_package and the monolithic windows / windows-sys crates stay on the win32metadata reference winmd as frozen legacy. They do not migrate to the flat in-house winmd: the reference winmd's 337 editorial sub-namespaces map 1:1 to 337 Cargo features, and a flat swap would collapse the published surface into one feature-less module — a breaking change not worth making. The reference winmd is retained at crates/tools/package/reference/ and tool_package points --in at it explicitly. Once nothing consumes the reference winmd, windows-bindgen's to_const_type / to_const_ptr const-wrapper reconstruction (a compatibility shim for the reference winmd's missing const string wrappers) becomes dead and can be removed.

Outstanding work

  • Round-trip asymmetries. A few RDL ↔ winmd forms don't round-trip byte-identically (raw identifiers, GUID constants, delegate ABI spelling). The winmd is correct either way; this is a cosmetic writer-side gap.