Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
c146764
Classic-COM vertical: runtime + classic/interop codegen + SMTC/DTM + …
yeelam-gordon Jul 22, 2026
197a84f
codegen(com): emit enums as const-object + companion type, not `const…
yeelam-gordon Jul 22, 2026
f9fb23e
codegen(com): fail closed on unknown base-chain shape + fix handle ou…
yeelam-gordon Jul 22, 2026
3fb57a9
codegen(com): reject non-interface TypeDefs in parse_com_interface_fr…
yeelam-gordon Jul 22, 2026
b415011
Round-5 review fixes: hwnd cache, doc wording, RC ambiguity, u64 f64 …
yeelam-gordon Jul 22, 2026
cbe6610
codegen(com): fail closed when a base classic-COM interface can't be …
yeelam-gordon Jul 22, 2026
a935328
Round-7 review fixes: harden base_slot dedup + robust default-IID lookup
yeelam-gordon Jul 22, 2026
7fb33cb
Flat-Win32 vertical (on classic): flat runtime + flat codegen + registry
yeelam-gordon Jul 22, 2026
2caec0b
codegen(flat): fix PSTR marshalling, opaque-pointer .d.ts type, BSTR …
yeelam-gordon Jul 22, 2026
b20ceab
codegen(flat): keep string buffers alive across flatInvoke call
yeelam-gordon Jul 22, 2026
3b9c1fa
codegen(flat)+napi: match enum d.ts convention; document flatInvoke b…
yeelam-gordon Jul 22, 2026
97e10ac
flat: fail loud on param/sig mismatch; project non-status integer ret…
yeelam-gordon Jul 22, 2026
875269c
flat: JSDoc return-shape uses sanitized JS names (not raw winmd)
yeelam-gordon Jul 22, 2026
e0c352a
flat: fix Handle in-out slot Buffer branch; doc updates
yeelam-gordon Jul 22, 2026
2fbd7ce
docs: update registry.js codegen comment to reflect landed [DllImport…
yeelam-gordon Jul 22, 2026
2a6d5a3
flat: correct Handle .d.ts contract — bigint|number, not bigint|Buffer
yeelam-gordon Jul 22, 2026
503a05d
docs: fix FlatAbiType PWStr/PStr/Handle doc comments
yeelam-gordon Jul 22, 2026
c099249
flat: also route Windows.Win32.Foundation.LSTATUS through I32 map
yeelam-gordon Jul 22, 2026
812bca0
flat: route [out]/[in,out] PWStr/PStr to OpaquePointer and guard void…
yeelam-gordon Jul 22, 2026
d527c1d
flat: type pointer-like .d.ts returns as bigint to match runtime
yeelam-gordon Jul 22, 2026
42cb113
flat: dedupe referenced enums by (namespace, name) and fail loud on c…
yeelam-gordon Jul 22, 2026
ce7e610
Merge remote-tracking branch 'origin/main' into feat/win32-com-tier1
yeelam-gordon Jul 22, 2026
84919b1
Fix: set is_flags on new classic-COM test enum construction (merge or…
yeelam-gordon Jul 22, 2026
9b9e46c
Merge branch 'feat/win32-com-tier1' into feat/win32-flat-tier2
yeelam-gordon Jul 22, 2026
76cd45f
Fix: set is_flags on new flat-Win32 test enum construction (merge ori…
yeelam-gordon Jul 22, 2026
bc0c8bb
Classic-COM codegen fixes: u16Type + HRESULT-in + out-string buffers …
yeelam-gordon Jul 23, 2026
ef275d1
Merge branch 'feat/win32-com-tier1' into feat/win32-flat-tier2
yeelam-gordon Jul 23, 2026
287cb98
Flat-Win32 codegen/runtime: full return-value ABI support (void/i64/u…
yeelam-gordon Jul 23, 2026
a4a26c2
Fix u16/i16 arg-wrapper codegen: emit DynWinRtValue.u16()/i16() not *…
yeelam-gordon Jul 23, 2026
1e454c0
Merge branch 'feat/win32-com-tier1' into feat/win32-flat-tier2
yeelam-gordon Jul 23, 2026
0f3379e
codegen(com): qualify interface registration names
lei9444 Jul 23, 2026
6a065a5
Merge branch 'feat/win32-com-tier1' of https://github.com/yeelam-gord…
lei9444 Jul 23, 2026
fd146bd
Flat codegen: decode BOOL returns and BOOL out-slots as boolean (matc…
yeelam-gordon Jul 23, 2026
55e04f8
Merge branch 'feat/win32-com-tier1' into feat/win32-flat-tier2
yeelam-gordon Jul 23, 2026
c2ddbd1
Flat codegen: classify RegConnectRegistryEx return as status (metadat…
yeelam-gordon Jul 23, 2026
b0758ed
Flat codegen: handle-typedef JSDoc references the actual handle type,…
yeelam-gordon Jul 23, 2026
fdb9391
Fix IMapView\2 parameterized IID constant (was inconsistent with code…
yeelam-gordon Jul 23, 2026
f608c8f
Flat codegen: recompute referenced_enums from kept methods after filt…
yeelam-gordon Jul 23, 2026
efb38f3
Merge branch 'feat/win32-com-tier1' into feat/win32-flat-tier2
yeelam-gordon Jul 23, 2026
f6679c1
docs(flat): document DLL residency / 'Ptr' return validity in flatInvoke
yeelam-gordon Jul 23, 2026
73f09f1
Classic-COM codegen: project scalar [out] pointer params as scalar ou…
yeelam-gordon Jul 23, 2026
bf0ac4a
Merge branch 'feat/win32-com-tier1' into feat/win32-flat-tier2
yeelam-gordon Jul 23, 2026
54ff3ba
Flat codegen: coerce unsigned-enum high-bit values across the u32 bou…
yeelam-gordon Jul 23, 2026
0227f8b
docs(flat): correct narrow-string helper comment about Buffer inputs
yeelam-gordon Jul 23, 2026
50a13fc
Flat codegen: fail-loud skip methods with by-value struct (unmappable…
yeelam-gordon Jul 23, 2026
5ff8be1
test+docs(flat): gate WinRT-generation test on SDK winmd; warn about …
yeelam-gordon Jul 23, 2026
16ba34f
test(flat): invoke the pre-built codegen binary via CARGO_BIN_EXE, no…
yeelam-gordon Jul 23, 2026
459ebc9
refactor(com): isolate classic COM from WinRT
lei9444 Jul 23, 2026
bd2e90f
Flat codegen: type handle out-slot return fields as bigint to match r…
yeelam-gordon Jul 23, 2026
2e7549f
docs(flat): correct FlatAbiType::Unknown comment (fail-loud skip, not…
yeelam-gordon Jul 23, 2026
2bbf9b4
Merge feat/win32-com-tier1 (classic-COM/WinRT isolation refactor) int…
yeelam-gordon Jul 23, 2026
56286b3
Harden ABI safety + fix self-review findings (pointer adoption, u64 r…
yeelam-gordon Jul 23, 2026
9ce6588
Fix u64 regression: accept both number and bigint (WinRT codegen pass…
yeelam-gordon Jul 23, 2026
96b971c
Flat codegen: fail-loud skip methods with 64-bit/float-underlying enu…
yeelam-gordon Jul 23, 2026
60d176a
fix(test): assert interop return is DynWinRtValue bridge, not DataTra…
yeelam-gordon Jul 23, 2026
10c95a4
fix(com): make take_raw_pointer error message ownership-neutral
yeelam-gordon Jul 24, 2026
f5ac4c0
test(win32): env-overridable winmd path + fix stale post-refactor COM…
yeelam-gordon Jul 24, 2026
50193b4
fix(com): memory-safety hardening — reject object in pointer(), owner…
yeelam-gordon Jul 24, 2026
7b7a49d
Merge #65 (feat/win32-com-tier1) into flat tier2
yeelam-gordon Jul 24, 2026
570915e
fix(flat): cache loaded modules process-lifetime to fix Ptr-return da…
yeelam-gordon Jul 24, 2026
16d293f
fix(flat): review follow-ups — flatInvoke doc + handle arg safe-integ…
yeelam-gordon Jul 24, 2026
123d172
fix: review follow-ups — u64 number validation, pointer doc, mutex-po…
yeelam-gordon Jul 24, 2026
98b3f99
fix: review follow-ups — avoid holding cache lock during LoadLibraryW…
yeelam-gordon Jul 24, 2026
8ff598a
test(flat): env-overridable winmd path (DYNWINRT_WIN32_WINMD)
yeelam-gordon Jul 24, 2026
950bbb7
fix(flat): validate numeric handles in in/out slot writes (_handleU64…
yeelam-gordon Jul 24, 2026
09efba1
test: add missing regression tests for prior flat/u64/mutex fixes
yeelam-gordon Jul 24, 2026
799c428
Fix COM handle typedef marshalling
yeelam-gordon Jul 24, 2026
db7b529
test(com): add regression test for iid_pointer owner-backed fix (#4)
yeelam-gordon Jul 24, 2026
c1542ea
Merge remote-tracking branch 'fork/feat/win32-com-tier1' into feat/wi…
yeelam-gordon Jul 24, 2026
02d684d
fix(flat): case-insensitive module-cache key + regression test
yeelam-gordon Jul 24, 2026
abc080e
fix(flat): widen opaque pointer .d.ts to accept Uint8Array
yeelam-gordon Jul 24, 2026
7d0bf65
docs(pointer): document number and Uint8Array in the accepted-inputs …
yeelam-gordon Jul 24, 2026
d052b52
Merge main into feat/win32-flat-tier2
lei9444 Jul 30, 2026
11ddfb5
Merge latest main into feat/win32-flat-tier2
lei9444 Jul 31, 2026
757ea2a
Merge latest main into feat/win32-flat-tier2
lei9444 Jul 31, 2026
169544a
Harden and isolate flat Win32 support
lei9444 Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/skills/classic-com-abi/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ Use this skill for changes under:

- `crates/dynwinrt/src/com.rs`, `signature.rs`, `native_call.rs`, or `call.rs`;
- `bindings/js/src/com.rs`;
- `crates/dynwinrt/src/win32.rs`;
- `bindings/js/src/win32.rs`;
- `tools/dynwinrt-codegen/src/com_metadata.rs`;
- `tools/dynwinrt-codegen/src/codegen/com/`; or
- Classic COM runners in `tests/runners/com/`.
- `tools/dynwinrt-codegen/src/codegen/win32/`;
- Classic COM runners in `tests/runners/com/`; or
- flat Win32 runners in `tests/runners/flat/`.

Read [`docs/classic-com-support.md`](../../../docs/classic-com-support.md)
before changing supported types or claiming support for an interface.
Expand All @@ -32,6 +36,20 @@ Windows.Win32.winmd facts
`Buffer`, `bigint`, `string`, and generated wrappers are projection choices.
They must not determine native semantics.

Flat Win32 is a third semantic frontend:

```text
Windows.Win32.winmd [DllImport]
-> flat-local metadata and validated ABI plan
-> flat JavaScript projection
-> @microsoft/dynwinrt/win32
-> System32 DLL export
```

Keep it out of `DynWinRt*`, the npm root, and `DynCom*`. Preserve P/Invoke
calling convention, architecture, pointer depth, native-array size
relationships, `SupportsLastError`, and return lifetime before rendering.

## Required semantic model

Preserve these facts before rendering:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,5 @@ jobs:
bindings/js/dist/winrt.d.ts
bindings/js/dist/com.js
bindings/js/dist/com.d.ts
bindings/js/dist/win32.js
bindings/js/dist/win32.d.ts
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ mono_crash.*
x64/
x86/
[Ww][Ii][Nn]32/
!tools/dynwinrt-codegen/src/codegen/win32/
!tools/dynwinrt-codegen/src/codegen/win32/**
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ package root remains the WinRT-only API. See
[Classic COM support](docs/classic-com-support.md) for the supported ABI,
common-interface test matrix, unsupported native types, and ownership rules.

Flat Win32 bindings use the separate `@microsoft/dynwinrt/win32` runtime
entrypoint and are generated into namespace-specific modules. Generation is
fail-closed for native shapes whose pointer depth, size, architecture, calling
convention, lifetime, or ownership is not modeled. See
[Flat Win32 support](docs/flat-win32-support.md) for the supported subset and
limitations.

Generated bindings project unambiguous public WinRT activation metadata as JavaScript
constructors, including overloads such as `new Uri(base, relative)`. Existing
static factory methods remain available. Classes that can only be returned by
Expand Down
10 changes: 10 additions & 0 deletions bindings/js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ native output that transfers an existing `+1` reference; numeric pointers and
typed-array pointers are borrowed and cannot be adopted. Win32 handles are not
COM references and require their own type-specific cleanup function.

Flat Win32 DLL exports use a third entrypoint:

```js
const { DynWin32 } = require('@microsoft/dynwinrt/win32');
```

Use generated flat Win32 wrappers rather than calling `DynWin32.invoke`
directly. Generated wrappers encode the validated native signature and capture
`GetLastError` when required.

Unambiguous public WinRT activation metadata is projected as JavaScript constructors.
Parameterized and composable activations support idiomatic forms such as
`new Uri(base, relative)` and `new StackPanel()`. The generated static factory
Expand Down
50 changes: 50 additions & 0 deletions bindings/js/__test__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from '../dist/winrt.js'
import * as winrtRuntime from '../dist/winrt.js'
import { DynCom, DynComMethodSig } from '../dist/com.js'
import { DynWin32 } from '../dist/win32.js'

test('Classic COM is isolated from the WinRT root entrypoint', (t) => {
t.false(Object.prototype.hasOwnProperty.call(winrtRuntime, 'DynCom'))
Expand Down Expand Up @@ -58,6 +59,55 @@ test('Classic COM is isolated from the WinRT root entrypoint', (t) => {
t.regex(esm.stdout, /runtime-entrypoints-ok/)
})

test('flat Win32 is isolated from the WinRT root entrypoint', (t) => {
t.false(Object.prototype.hasOwnProperty.call(winrtRuntime, 'DynWin32'))
t.truthy(DynWin32)

const assertion =
"const assert = require('node:assert/strict');" +
"const winrt = require('@microsoft/dynwinrt');" +
"const win32 = require('@microsoft/dynwinrt/win32');" +
"assert.equal(Object.prototype.hasOwnProperty.call(winrt, 'DynWin32'), false);" +
"assert.equal(typeof win32.DynWin32, 'function');" +
"console.log('win32-entrypoint-ok')"
const cjs = spawnSync(process.execPath, ['--eval', assertion], {
cwd: resolve(process.cwd()),
encoding: 'utf8',
windowsHide: true,
})
t.is(cjs.status, 0, cjs.stderr)
t.regex(cjs.stdout, /win32-entrypoint-ok/)
})

test('DynWin32 validates scalar widths and retained pointer storage', (t) => {
t.notThrows(() => DynWin32.i64(-(2n ** 63n)))
t.notThrows(() => DynWin32.i64(Number.MAX_SAFE_INTEGER))
t.throws(() => DynWin32.i64(2n ** 63n), { message: /signed 64-bit/ })
t.throws(() => DynWin32.i64(1.5), { message: /safe integer/ })
t.throws(() => DynWin32.i8(128), { message: /range/ })
t.throws(() => DynWin32.i8(4_294_967_297), { message: /range/ })
t.throws(() => DynWin32.i32(4_294_967_296), { message: /range/ })
t.throws(() => DynWin32.u32(-1), { message: /range/ })
t.throws(() => DynWin32.u16(1.5), { message: /integer/ })
t.is(DynWin32.toPointerBigint(DynWin32.handle(-1n)), (2n ** 64n) - 1n)
t.is(DynWin32.toPointerBigint(DynWin32.handle(-2)), (2n ** 64n) - 2n)

const bytes = new Uint8Array(8)
const pointer = DynWin32.pointer(bytes)
structuredClone(bytes.buffer, { transfer: [bytes.buffer] })
const error = t.throws(() => DynWin32.toPointerBigint(pointer))
t.regex(error.message, /backing ArrayBuffer is detached/)
})

test('DynWinRtValue accepts lossless UInt64 bigint inputs', (t) => {
const max = (2n ** 64n) - 1n
t.is(DynCom.toU64Bigint(DynWinRtValue.u64(max)), max)
t.throws(() => DynWinRtValue.u64(-1n), { message: /unsigned 64-bit/ })
t.throws(() => DynWinRtValue.u64(Number.MAX_SAFE_INTEGER + 1), {
message: /safe integer/,
})
})

test('DynCom rejects pointers after their TypedArray backing store is detached', (t) => {
const bytes = new Uint8Array(16)
const pointer = DynCom.pointer(bytes)
Expand Down
9 changes: 9 additions & 0 deletions bindings/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@
"require": "./dist/com.js",
"default": "./dist/com.js"
},
"./win32": {
"types": "./dist/win32.d.ts",
"import": "./dist/win32.js",
"require": "./dist/win32.js",
"default": "./dist/win32.js"
},
"./package.json": "./package.json"
},
"typesVersions": {
"*": {
"com": [
"dist/com.d.ts"
],
"win32": [
"dist/win32.d.ts"
]
}
},
Expand Down
10 changes: 8 additions & 2 deletions bindings/js/scripts/generate-entrypoints.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,24 @@ const comExports = new Set([
'WinGuid',
'WinGUID',
])
const win32Exports = new Set(['DynWin32', 'DynWin32CallResult', 'DynWin32Value'])

writeFacade(
'winrt',
nativeExports.filter((name) => !name.startsWith('DynCom')),
nativeExports.filter((name) => !name.startsWith('DynCom') && !name.startsWith('DynWin32')),
)
writeFacade(
'com',
nativeExports.filter((name) => comExports.has(name)),
)
writeFacade(
'win32',
nativeExports.filter((name) => win32Exports.has(name)),
)

function writeFacade(name, exports) {
const missing = name === 'com' ? [...comExports].filter((value) => !exports.includes(value)) : []
const required = name === 'com' ? comExports : name === 'win32' ? win32Exports : new Set()
const missing = [...required].filter((value) => !exports.includes(value))
if (missing.length > 0) {
throw new Error(`Missing required ${name} exports: ${missing.join(', ')}`)
}
Expand Down
6 changes: 3 additions & 3 deletions bindings/js/src/com.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ fn uint8_array_info(
}))
}

fn pointer(value: Unknown) -> napi::Result<DynWinRTValue> {
pub(super) fn pointer(value: Unknown) -> napi::Result<DynWinRTValue> {
use napi::sys;

let env = value.value().env;
Expand Down Expand Up @@ -364,7 +364,7 @@ fn adopt_co_task_mem_pointer(value: &mut DynWinRTValue) -> napi::Result<DynWinRT
))
}

fn as_pointer_bigint(value: &DynWinRTValue) -> napi::Result<BigInt> {
pub(super) fn as_pointer_bigint(value: &DynWinRTValue) -> napi::Result<BigInt> {
validate_pointer_owner(value)?;
let bits = match &value.0 {
dynwinrt::WinRTValue::Object(_) => {
Expand Down Expand Up @@ -414,7 +414,7 @@ fn take_bstr(value: &mut DynWinRTValue) -> napi::Result<String> {
String::try_from(&value).map_err(|error| napi::Error::from_reason(error.to_string()))
}

fn validate_pointer_owner(value: &DynWinRTValue) -> napi::Result<()> {
pub(super) fn validate_pointer_owner(value: &DynWinRTValue) -> napi::Result<()> {
if let Some(owner) = &value.1 {
owner.validate()?;
}
Expand Down
33 changes: 30 additions & 3 deletions bindings/js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ use std::{

use dynwinrt;
use napi::Env;
use napi::bindgen_prelude::{BigInt, PromiseRaw};
use napi::bindgen_prelude::{BigInt, Either, PromiseRaw};
use napi::threadsafe_function::ThreadsafeFunctionCallMode;
use napi_derive::napi;
use windows::core::{HSTRING, IUnknown, Interface};

mod com;
pub use com::{DynCom, DynComInterface, DynComMethodHandle, DynComMethodSig, DynComType};
mod win32;
pub use win32::{DynWin32, DynWin32CallResult, DynWin32Value};
mod async_promise;
mod scheduled_start;

Expand Down Expand Up @@ -693,8 +695,33 @@ impl DynWinRTValue {
DynWinRTValue::new(dynwinrt::WinRTValue::I64(value))
}
#[napi]
pub fn u64(value: i64) -> DynWinRTValue {
DynWinRTValue::new(dynwinrt::WinRTValue::U64(value as u64))
pub fn u64(
#[napi(ts_arg_type = "number | bigint")] value: Either<BigInt, f64>,
) -> napi::Result<DynWinRTValue> {
let value = match value {
Either::A(value) => {
let (negative, value, lossless) = value.get_u64();
if negative || !lossless {
return Err(napi::Error::from_reason(
"DynWinRtValue.u64(): value must fit in an unsigned 64-bit integer",
));
}
value
}
Either::B(value) => {
if !value.is_finite()
|| value < 0.0
|| value.fract() != 0.0
|| value > 9_007_199_254_740_991.0
{
return Err(napi::Error::from_reason(
"DynWinRtValue.u64(): number must be a non-negative safe integer",
));
}
value as u64
}
};
Ok(DynWinRTValue::new(dynwinrt::WinRTValue::U64(value)))
}
#[napi]
pub fn f32(value: f64) -> DynWinRTValue {
Expand Down
Loading
Loading