dynwinrt-codegen

September 15, 2026 · View on GitHub

dynwinrt-codegen reads Windows metadata (.winmd) and generates typed bindings for dynwinrt:

  • WinRT JavaScript (.js) with TypeScript declarations (.d.ts)
  • WinRT Python (.py) with type stubs (.pyi) and a py.typed marker
  • Supported Classic COM APIs from Windows.Win32.winmd as JavaScript and TypeScript
  • Contract-driven Win32 DLL exports from Windows.Win32.winmd as JavaScript and TypeScript

The command is available for Windows x64 and ARM64. Generated JavaScript uses @microsoft/dynwinrt; generated Python uses dynwinrt.

Install

Python

python -m pip install --pre dynwinrt-codegen
dynwinrt-codegen generate --namespace Windows.Foundation --class-name Uri `
  --lang py --output .\generated

The Python distribution is a standalone native command. Its py3-none-win_amd64 and py3-none-win_arm64 wheels require Python 3.8–3.14 but do not require Cargo or Rust. Generated Python package manifests require CPython 3.11–3.14 and pin dynwinrt to the generator's exact version.

npm

npm install @microsoft/dynwinrt
npm install --save-dev @microsoft/dynwinrt-codegen
npx dynwinrt-codegen generate --namespace Windows.Foundation --class-name Uri `
  --output .\generated

Generate

dynwinrt-codegen generate [OPTIONS]

Use npx dynwinrt-codegen instead of dynwinrt-codegen when running the npm package.

OptionDescription
--winmd PATH[;PATH...]Metadata file paths. Sibling .winmd files are discovered automatically. The Windows SDK is auto-detected when no input supplies Windows.* metadata.
--winmd-list FILENewline-separated metadata paths to emit; blank lines and # comments are ignored.
--folder DIRLoad every .winmd file directly inside a directory.
--namespace NSGenerate one namespace. Without it, generate all non-Windows.* namespaces in the input.
--class-name NAME[,NAME...]Generate specific classes, public interfaces, or native Apis containers. Use fully qualified names, or unqualified names together with --namespace.
--ref PATH[;PATH...]Metadata used only for type resolution. Sibling discovery is disabled for references.
--ref-list FILENewline-separated reference metadata paths; blank lines and # comments are ignored.
--lang js|pyjs emits CommonJS .js, an ESM facade, and .d.ts files (default); py emits .py, .pyi, and py.typed.
--output DIRDedicated codegen-owned output directory (default ./generated). Existing contents may be replaced or removed.
--import-name NAMERuntime package imported by generated JavaScript (default @microsoft/dynwinrt).
--dry-runValidate metadata, dependencies, ABI contracts, and layout without writing files.
--pyiWith --lang py, explicitly request the default type stubs; retained for compatibility.
--no-pyiWith --lang py, omit .pyi files and py.typed.

Examples

Generate two Windows SDK classes as JavaScript and TypeScript:

dynwinrt-codegen generate `
  --namespace Windows.Storage `
  --class-name StorageFile,StorageFolder `
  --output .\generated

Generate Python from a restored metadata folder:

dynwinrt-codegen generate `
  --folder C:\path\to\metadata `
  --lang py `
  --output .\generated-python

Load emitted metadata and reference metadata from list files:

dynwinrt-codegen generate `
  --winmd-list .\winmd-inputs.txt `
  --ref-list .\winmd-references.txt `
  --output .\generated

Validate a generation request without changing the output directory:

dynwinrt-codegen generate `
  --folder C:\path\to\metadata `
  --dry-run

Contract-driven flat Win32

Generate JavaScript bindings and TypeScript declarations for Win32 DLL exports from Windows.Win32.winmd. Built-in contracts are validated against the pinned Microsoft.Windows.SDK.Win32Metadata 71.0.14-preview package. Metadata provides native ABI facts; the independent Win32 registry supplies explicit ownership, buffer, and lifecycle evidence where required.

dynwinrt-codegen generate `
  --winmd $env:DYNWINRT_WIN32_WINMD `
  --class-name "Windows.Win32.System.SystemInformation.Apis,Windows.Win32.System.Registry.Apis" `
  --output .\generated

--namespace Windows.Win32.System.Registry is equivalent to selecting its Apis container. Unsupported shapes are explicitly diagnosed. Generated modules live under win32/windows/win32/system/registry/ and win32/windows/win32/system/system-information/, outside the WinRT root. The runtime package must include the matching win32 entrypoint. The win32-census --winmd <PATH> --json command reports eligible exports and complete projections. Support requires validated ABI, ownership, and lifecycle contracts; unsupported APIs remain explicit diagnostics. See the supported capabilities and contract architecture.

Other commands

dynwinrt-codegen capabilities prints the command's supported features, one machine-readable value per line.

dynwinrt-codegen com-census --winmd <PATH> [--json] measures how many eligible interfaces in Windows.Win32.winmd have complete safe Classic COM generation.

Generated output

The generator resolves transitive dependencies and emits namespace index files. WinRT output includes typed classes and interfaces, public activation constructors, static factory methods, enums, structs, delegates, async operations, and generic collections.

JavaScript implementation modules are CommonJS, with index.mjs facades for ESM consumers, and need no TypeScript compilation step. Python output uses snake_case names and includes type information by default. Documentation from sibling XML files is included when available.

Classic COM generation is available only with --lang js. It is isolated in a com subpackage and fails closed when metadata does not provide enough ABI, layout, ownership, or cleanup information. See the Classic COM usage guide.

The output directory belongs to codegen; do not store handwritten files in it. After changing metadata files, SDK versions, or reference inputs, regenerate the complete output. Python module components longer than 120 characters are shortened with a stable readable prefix and hash suffix while public type names remain unchanged.

Implementation helper names are allocated alongside metadata types and follow their owning interfaces through incremental renames. Python's package-level root exports drop an interface and its helpers when its short name becomes ambiguous, while namespace imports remain available. Python's heterogeneous interface-pair union is generated from validated inventory records, not inferred from .pyi text. The first typed incremental generation over an older inventory needs the original WinMD/--ref inputs for interfaces whose implementation records are missing. If those inputs are unavailable, generation fails atomically and leaves the previous output usable: supply the listed metadata and retry, or fully regenerate the package. New inventories retain these records, and --no-pyi does not require this typing migration. After --no-pyi, include the earlier types when regenerating with stubs enabled (or fully regenerate the package). A typed append fails rather than publishing imports of missing retained declarations or dropping interfaces from the union.

The npm wrapper accepts the legacy --source-map, --declaration, and --no-declaration flags as no-ops. The Rust command accepts only js and py for --lang.

Build and test from source

From the repository root:

cargo build -p dynwinrt-codegen --release
cargo test -p dynwinrt-codegen

Official npm and PyPI packages are built and published by the repository release pipelines.

Win32 result contracts

Flat Win32 generation uses dynwinrt-win32-contracts as its single wire-protocol authority. Native metadata still determines pointer depth, constness, encoding, type identity, and ownership provenance. After caller-owned buffers become physical input pointers, projection resolves a version 3 descriptor covering every direct return, native output cell, and declared aggregate result field. Version 1 and slot-only version 2 descriptors remain readable; aggregate field targets require version 3.

contracts\win32\function-contracts.json accepts result-contract effects containing the shared ResultContract type. These replace the default or upgraded policy for that native target; legacy call-contract evidence remains readable. New generation uses upgrade_metadata(): legacy ownership evidence alone does not establish defined output storage on failure, so owned failure results default to undefined. An explicit result policy may instead guarantee a defined failure result, including a discarded owned value requiring cleanup. Historical version 1 cleanup behavior is confined to compatibility decoding; ordinary scalar/status/count defaults remain unchanged. Success, failure, and disjoint conditional overrides distinguish undefined storage from defined values, ownership/cleanup, and delivery/discard. Projection derives nullable results and resource conversions from those policies; renderers neither infer ownership nor bind native functions on import.

An Out/InOut aggregate pointer retains its exact layout in pointeeDescriptor, separately from the by-value-only aggregateDescriptor. Its physical ABI remains Pointer/In, not pointer-to-pointer. The native descriptor's outputFields lists evidenced result fields in native declaration order; aggregate-field targets index that list and the native parameter, not the physical output cells. PROCESS_INFORMATION therefore has four field results: two independently owned CloseHandle resources and two scalar IDs. All four default to undefined on failure unless reviewed result evidence states otherwise.

Generated builders, native-struct arguments, and pointee specifications share one descriptor constant. Native invocation prepares and registers field lifetimes before any return conversion or JavaScript wrapping; generated code does not call the manual prepareNativeStructCall/markNativeStructCallResult helpers. The caller still owns the structure bytes, and existing field getters and take-resource helpers keep their names and types. Unknown owned arrays, unions, and nested result shapes fail closed.

The production registry schema references generated contracts\win32\call-contract.schema.json, not a second handwritten protocol. After changing the shared protocol or reviewed registry data, update its schema and LF-normalized production manifest hashes using the existing test runner:

$env:DYNWINRT_UPDATE_WIN32_SCHEMA = '1'
cargo test -p dynwinrt-codegen --test win32_contract_schema_test
Remove-Item Env:DYNWINRT_UPDATE_WIN32_SCHEMA

Without the update variable, the same test checks schema and manifest consistency. Synthetic result matrices cover failure-only cleanup/delivery combinations; metadata regressions and complete CommonJS/ESM/TypeScript module tests preserve the pinned 8,936/18,321 export census.

License

MIT