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 apy.typedmarker - Supported Classic COM APIs from
Windows.Win32.winmdas JavaScript and TypeScript - Contract-driven Win32 DLL exports from
Windows.Win32.winmdas 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.
| Option | Description |
|---|---|
--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 FILE | Newline-separated metadata paths to emit; blank lines and # comments are ignored. |
--folder DIR | Load every .winmd file directly inside a directory. |
--namespace NS | Generate 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 FILE | Newline-separated reference metadata paths; blank lines and # comments are ignored. |
--lang js|py | js emits CommonJS .js, an ESM facade, and .d.ts files (default); py emits .py, .pyi, and py.typed. |
--output DIR | Dedicated codegen-owned output directory (default ./generated). Existing contents may be replaced or removed. |
--import-name NAME | Runtime package imported by generated JavaScript (default @microsoft/dynwinrt). |
--dry-run | Validate metadata, dependencies, ABI contracts, and layout without writing files. |
--pyi | With --lang py, explicitly request the default type stubs; retained for compatibility. |
--no-pyi | With --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.