The primary objective of this project is to expose WinRT Windows APIs to dynamic languages, specifically JavaScript and Python, while ensuring a seamless developer experience. This approach eliminates the need for native compilation (such as C++ compilers) and removes strict version coupling between the projection and the Windows App SDK (WASDK) components.
Current static projections, such as existing PyWinRT, necessitate the generation and compilation of native code for specific versions of WinRT components. This requirement results in a complex compatibility matrix and mandates the release of new projection packages for every WASDK release, and more importantly, does not expose full WinRT APIs to developers due to AOT compilation constraints.
Developers in the Node.js, Electron, and Python ecosystems expect a streamlined setup process, typically via npm install or pip install. They should not be required to configure MSVC tools or integrate C++ compilers into their build chains.
A key distinction exists between static and dynamic language projections:
- Static Languages (C++, Rust, C#): These languages possess full type information at compile time, allowing static projections to tailor bindings precisely to what is utilized.
- Dynamic Languages (JavaScript, Python): These rely on runtime evaluation, where objects are often type-erased. Static projections can create gaps when a function encounters an unknown WinRT object at runtime that was not statically projected.
The proposed architecture transitions from static native bindings to a dynamic approach, comprising two primary, separable components:
This component is a minimal runtime library native to the target ecosystem (e.g., a .pyd module or Node.js addon) that facilitates dynamic calls to arbitrary WinRT APIs.
A minimal dynamic Foreign Function Interface (FFI) layer is responsible for:
- Invoking arbitrary WinRT methods via function pointers with correct parameter type information, typically leveraging
dyncallorlibffi. - Managing
outparameters through stack allocation. - Support of WinRT type system, especially runtime generic type system.
- Supporting direct pass-by-value for value types, computing struct sizes and alignments at runtime in the absence of
sizeof.
This infrastructure can be mostly shared across dynamic languages.
- The library wraps fundamental OS APIs, including string handling,
RoInitialize,RoGetActivationFactory, andQueryInterface. - WinAppSDK Bootstrap: Including the necessary bootstrap DLL and native interop to enable WinAppSDK usage for unpackaged applications.
This infrastructure can be mostly shared across dynamic languages.
- Mapping WinRT
HSTRINGs to native language strings. - Converting WinRT
HRESULTs into language-specific exceptions. - Transforming
IAsyncActioninto language-specific Promises or Awaitables.
This component bridges the gap between raw WinMD metadata and the runtime projection, operating in two distinct modes:
In this mode, the runtime parses .winmd files on the fly as APIs are accessed.
- Advantages: Proven stability in previous JavaScript projections and simpler distribution (no generation step required).
- Disadvantages: Incurs runtime parsing overhead (potentially negligible compared to marshalling) and lacks IDE IntelliSense support.
A CLI tool parses .winmd files to generate non-native code (pure .js or .py files) that defines interface shapes and method signatures for the runtime. This mode can also generate IDE helpers, such as TypeScript .d.ts files and Python .pyi stubs.
- Advantages: Enhanced Developer Experience (IntelliSense/Autocomplete) and faster startup times (eliminating WinMD parsing).
- Disadvantages: Requires a generation step, although strictly without native compilation.
The usage workflow involves two stages:
The runtime is distributed as a generic library for the target language (e.g., npm install @microsoft/dynwinrt or pip install dynwinrt).
Developers have two options for using the projection:
- Direct Usage: The runtime directly supports runtime interface specifications. Developers can use libraries directly by lazily loading namespaces with distributed WinMDs. The runtime parses the WinMDs and generates necessary interface shapes on the fly.
- Generated Bindings: Alternatively, developers can execute a tool (e.g.,
npx dynwinrt-codegen generate ...) to generate bindings and types specifically for the WinMDs they intend to use.
The primary performance costs are attributed to WinMD parsing (in lazy mode) and the overhead of dynamic WinRT method invocation (dynamic dispatch). This invocation overhead is expected to be comparable to existing marshalling costs associated with crossing the JavaScript/Python boundary.
Adopting a hybrid approach—specifically, the design-time generation of interface shapes—can eliminate runtime WinMD parsing costs, reducing overhead strictly to FFI operations.
The runtime provides a minimal representation of WinRT types and values, along with conversions to language-native equivalents. It also offers a minimal interface specification language for the target language, enabling users to define WinRT interfaces and classes at runtime.
TypeScript interface as demonstrated below,
thus with the minimum runtime provided by @microsoft/dynwinrt,
all WinRT APIs can be specified and invoked dynamically using the target language.
Illustrative sketch only — the runtime API surface actually shipped is
DynWinRtType/DynWinRtValue/DynWinRtMethodHandle, described inbindings/js/README.md. TheWinRT.Interface({...})shape below is design-language pseudocode kept for historical continuity with the original Lazy-WinRT prototype.
import { WinRT } from "@microsoft/dynwinrt";
const UriInterface = WinRT.Interface({
namespace: "Windows.Foundation",
name: "IUriRuntimeClass",
guid: "<...guid of the interface...>",
methods: [
// get AbsoluteUri(): string
WinRT.Method([WinRT.Out(WinRT.HSTRING)]), // Implicitly assumes first argument is ComPtr, result is HRESULT
// get Domain(): string
WinRT.Method([WinRT.Out(WinRT.HSTRING)]),
// ... other methods
],
});
const UriFactoryInterface = WinRT.Interface({
namespace: "Windows.Foundation",
name: "IUriRuntimeClassFactory",
guid: "<...guid of the interface...>",
methods: [
// CreateUri(string uri): IUriRuntimeClass
WinRT.Method([WinRT.HSTRING, WinRT.Out(UriInterface)]),
],
});
class Uri {
// Statically cached factory object in target dynamic language
static Factory = WinRT.as(UriFactoryInterface, WinRT.getActivationFactory("Windows.Foundation.Uri"));
constructor(uriString: string) {
this._instance = WinRT.callMethod(UriFactoryInterface, 7, [
uriString,
]);
}
get Host(): string {
return WinRT.callMethod(UriInterface, 11, [this._instance]); // 11 is the vtable index of get_Host
}
}The WinMD parser allows for the generation of these interface specifications and developer-friendly projection classes at design time or runtime. Async handling and generic type support (IVector<T>, IAsyncOperation<T>, etc.) are fully implemented in the core runtime.
To optimize performance, common method signatures can be implemented within the runtime library. This allows frequent operations to execute with speeds comparable to static projections. Since only the actual ABI signature is critical for these stub methods, a wide variety of WinRT methods can map to a single stub. For instance, getter-like or factory-like methods can often map to a unified signature:
// Common stub for object.get_X -> Com/HSTRING reference types
HRESULT Method_Out_Pointer(void* funPtr, ComPtr self, void* outValue) {
var f = // cast funPtr to proper function pointer type
return f(self, outValue);
}- Signature Casting: Proper handling of GUID casting is required, particularly for type-safety and generics.
- Representation Mapping: Special handling is needed for JavaScript/Python representations; for example,
IVectormay need to map to a function rather than a simple interface instance. - Async Operations: While mentioned, the handling of asynchronous operations requires robust implementation details.
- Legacy JS Projection: Historical JavaScript applications utilized a dynamic projection where the runtime read WinMDs, achieving acceptable performance.
- PyWinRT Static C++/WinRT-based projections demonstrated significant versioning and distribution challenges.
- lazy-winrt "Lazy-WinRT" Prototype: This prototype validated the feasibility and potential performance of parsing WinMDs and invoking methods dynamically.
- dynwinrt — Rust-based implementation inspired by Lazy-WinRT. Ships the core runtime, JS bindings via
napi-rs, Python bindings viaPyO3, and thedynwinrt-codegencode-generation tool.