Flat Win32 ([DllImport]) support: generated natural wrappers + Registry (stacks on #65) - #67
Open
Gordon Lam (yeelam-gordon) wants to merge 76 commits into
Open
Conversation
…shared napi plumbing Reorganizes the Win32/COM work into a self-contained classic-vertical that pairs the classic-COM runtime (call.rs RawPtr, classic_com.rs, signature::define_from_iunknown), classic-COM/interop codegen (codegen::com, main.rs --class-name COM path), and the shared napi plumbing (coCreateInstance, registerInterfaceUnknown, pointer, iidPointer, asPointerBigint, u64 Either fix, createTestHwnd) needed by the ITaskbarList3 / DTM / SMTC E2Es. Flat-Win32 pieces (flat_call.rs, codegen::flat, flatInvoke napi, Apis/DllImport meta) are intentionally absent from this branch and are layered back on top in reorg/flat-vertical. The classic E2Es (taskbarlist.mjs, dtm.mjs, smtc.mjs) acquire a process-owned HWND through a new tiny napi helper createTestHwnd() (delegates to CreateWindowExW via windows-rs) instead of the flatInvoke-based path used in the fully-integrated reference. A small hwnd.mjs helper module encapsulates the call. Gauntlet (green): - cargo test -p dynwinrt: 83 passed + 1 winrt_regression - cargo test -p dynwinrt-codegen: all suites green - napi build (release): OK - Node E2Es: taskbarlist.mjs / dtm.mjs / smtc.mjs PASS - tests\e2e_test.ps1 -SkipBuild: py 29/29, ts 28/28 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… enum` Mirror the WinRT enum generator (render_enum_dts in codegen/javascript/render/declarations.rs) so classic-COM enum .d.ts stays consumable under TS isolatedModules and matches the JS Object.freeze runtime shape. Snapshot updated (tests/snapshots/itaskbarlist3/TBPFLAG.d.ts); the .js output is unchanged so the taskbarlist E2E still works via TBPFLAG.TBPF_NORMAL member access. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t-param projection - meta::parse_com_interface_from_index now tracks explicit termination at IUnknown or IInspectable and returns None if the base-chain walk exits without reaching either, instead of silently defaulting to IInspectable (base offset 6). This prevents wrong absolute vtable slots when a winmd has an unexpected inheritance shape or missing interface_impls. - unwrap_return_js: for opaque Win32 handle out-params (HWND, PWSTR, ...) emit `.asPointerBigint()` instead of `.toI64()`. The runtime may produce WinRTValue::Object/RawPtr/Null when the handle's inner `Value` field is a void*-shaped type, and `.toI64()` panics on those variants (its fallback `.toNumber()` panics for non-numeric variants). `.asPointerBigint()` cleanly handles all three pointer representations. All snapshots and E2Es unaffected: current interfaces have no [out] handle params, and the two rooted tests (ITaskbarList3 → IUnknown, SMTC interop → IInspectable) still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…om_index Add an early guard that returns None unless the resolved TypeDef has `TypeAttributes::Interface` set. Prevents WinRT runtime classes, structs, enums, and delegates from being mis-parsed by walking their `interface_impls()` and flattening a bogus method list — which could have quietly routed `--class-name *Interop` runtime classes through the classic-COM code path in `main.rs`. Callers see `None` and fall through to the correct WinRT path. All existing tests + node/py/ts E2Es unaffected (real interfaces still have the Interface attribute set). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…path - napi::create_test_hwnd() now caches the HWND in an AtomicUsize so repeated calls in a long-lived Node process (tests, REPL, Electron) don't accumulate window handles. - codegen(com): handle typedef comments now say "Opaque Win32 handle or pointer newtype (e.g. HWND, PWSTR)" instead of just "Opaque Win32 handle" — the aliases cover both handles and pointer newtypes like PWSTR/PCWSTR. Snapshots updated. - meta::find_runtime_class_default_iid now collects all runtime-class matches for a simple name and refuses to pick when they resolve to distinct default-interface IIDs (cross-namespace collisions). Emits an explicit warning listing the candidates and returns None so callers fall through instead of silently generating interop wrappers with the wrong IID. - DynWinRTValue.u64 number branch now takes f64 (not i64) so we can reject NaN, +/-Infinity, and fractional values explicitly. napi's previous i64 coercion silently truncated fractions and mis-handled non-finite inputs. Bigint path and MAX_SAFE_INTEGER bound unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…parsed parse_com_interface_from_index previously logged a warning and continued when parse_interface_with_offset returned None for a base interface. That left slot_cursor unadvanced and produced a truncated flattened method list, so the leaf interface's absolute vtable indices would be off by however many base methods were missing. The debug_assert_eq! below caught this in debug builds, but in release it was silently compiled out — so codegen would emit wrappers that dispatch to the wrong COM methods. Now return None on any base-parse failure (with a warning naming both the missing base and the leaf we're refusing to emit), so callers see a clean skip rather than misgeneration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- MetadataTable::register_interface and register_interface_iunknown now
route through create_interface_method_table*(iid, base_slot) BEFORE
checking the name cache. That call is already assertive on base_slot
mismatch (arena.rs:131). Previously a first-time
register_interface(name, iid) with base_slot=6 would let a later
register_interface_iunknown(name, iid) — expecting base_slot=3 —
silently reuse the WinRT-shaped vtable and dispatch to the wrong
absolute slots. Now the mismatch panics loudly.
- meta::find_runtime_class_default_iid: replaced the `?` on the
default-interface TypeDef lookup with a `let-else { continue }`.
A missing/unreadable interface TypeDef for one candidate no longer
aborts the whole search — other matching runtime classes (or other
DefaultAttribute impls on the same class) can still resolve.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Stacks flat-Win32 support on top of reorg/classic-vertical, restoring
the full functionality of the reference tree at feat/win32-com-codegen:
- crates/dynwinrt/src/flat_call.rs — flat runtime (LoadLibrary +
GetProcAddress + libffi + last-error).
- tools/dynwinrt-codegen/src/codegen/flat.rs + parse_flat_apis /
FlatApisMeta in meta.rs — flat codegen from Apis/DllImport metadata.
- napi flat_invoke / flat_last_error exports.
- main.rs Apis/flat-codegen dispatch path.
- bindings/js/e2e/registry.{js,mjs} + flat_registry.mjs E2Es.
- tools/dynwinrt-codegen/tests/win32_flat_test.rs +
tests/snapshots/registry_apis/* snapshots.
Also restores the reference behaviour of the shared classic-COM E2Es
(taskbarlist.mjs / dtm.mjs / smtc.mjs) to use flatInvoke for HWND
acquisition, and drops the classic-only hwnd.mjs helper + napi
createTestHwnd() shim (no longer needed because flat is present again).
Gauntlet (green):
- cargo test -p dynwinrt: 96 passed + 1 winrt_regression
- cargo test -p dynwinrt-codegen: 15 win32_flat_test tests + all suites green
- napi build (release): OK
- Node E2Es: taskbarlist / registry / dtm / smtc / flat_registry PASS
- tests\e2e_test.ps1 -SkipBuild: py 29/29, ts 28/28
- git diff HEAD feat/win32-com-codegen: empty (tree identical to reference)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…safety Round-1 review fixes on top of the rebased flat-Win32 vertical: 1. PSTR/LPCSTR params were marshalled with _wideStringBuffer (UTF-16LE), which passes wrong bytes to ANSI/UTF-8 Win32 A-suffixed exports (e.g. RegOpenKeyExA) and can smash the callee's stack. Split into a new _narrowStringBuffer (UTF-8, NUL-terminated) and route FlatAbiType::PStr through it. Rejects embedded NUL for the same truncation-safety reason as the wide-string helper. 2. FlatAbiType::Unknown was typed as "unknown" in .d.ts but marshalled as DynWinRtValue.pointer(var) at runtime -- the .d.ts didn't match the runtime contract and silently accepted arbitrary JS values that would then crash inside DynWinRtValue.pointer(...) with a type error. Type it as (bigint | Buffer | null), matching Ptr/PtrTo(_). 3. BSTR (SysAllocString-owned, length-prefixed COM string) was mapped to FlatAbiType::PWStr -- this drops the 4-byte length prefix and can crash callees using SysStringLen. Map to FlatAbiType::Unknown so it surfaces as an opaque pointer parameter instead of silently mis-marshalling. Snapshot updated (57 insertions/34 deletions in Apis.js) -- all A-suffixed Registry exports now go through _narrowStringBuffer. Gauntlet (green): - cargo test -p dynwinrt: 96 passed + 1 winrt_regression - cargo test -p dynwinrt-codegen: 15 win32_flat + all other suites - Node E2Es: taskbarlist, registry, dtm, smtc, flat_registry -> PASS - repo e2e: py 29/29, ts 28/28 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round-2 review fix: DynWinRtValue.pointer(Buffer) extracts the Buffer's
as_ptr() but does NOT retain the Buffer itself. When a string wrapper
was called inline as `DynWinRtValue.pointer(_wideStringBuffer(x))` the
temporary Buffer became unreachable the moment `pointer(...)` returned,
so GC could reclaim it before `flatInvoke` reached the flat Win32 export
-- passing a dangling pointer to (e.g.) RegOpenKeyExW.
Fix: for every PWStr/PStr input parameter, emit a named local before
the flatInvoke call --
const _<jname>Buf = _wideStringBuffer(<jname>); // or _narrowStringBuffer
const _ret = DynWinRtValue.flatInvoke(..., [
..., DynWinRtValue.pointer(_<jname>Buf), ...
]);
-- and reference the local in the args array. The local's identifier is
reachable through the enclosing scope until the function returns, so JS
engines must keep the Buffer alive across the flat call. Same shape as
the out/in-out `_*Slot` Buffers, which have always been named locals.
Snapshot: Registry Apis.js now hoists every string Buffer to
`_<jname>Buf` before its flatInvoke call. No other test surface changes.
Gauntlet (green):
- cargo test -p dynwinrt-codegen: 15 win32_flat + all other suites
- Node E2Es: taskbarlist, registry, dtm, smtc, flat_registry -> PASS
- repo e2e: py 29/29, ts 28/28
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…uffer keep-alive
Round-2 review fixes.
1. Flat enum .d.ts was emitting `export declare const enum Foo { ... }`,
which diverges from the codebase convention (const object + companion
type) used by the WinRT/classic-COM emitter in
`tools/dynwinrt-codegen/src/codegen/javascript/render/declarations.rs`
and breaks TypeScript `isolatedModules` builds (Vite/esbuild/Next.js).
PR #1's earlier round 197a84f explicitly moved WinRT enums off
`const enum` for this reason; flat codegen must follow the same
convention. Now emits:
export type Foo = (typeof Foo)[keyof typeof Foo];
export declare const Foo: {
readonly A: 1;
readonly B: 2;
};
which mirrors the `Object.freeze({...})` runtime shape and is
`isolatedModules`-safe.
2. Documented the Buffer keep-alive contract on `DynWinRtValue.flatInvoke`
in `bindings/js/src/lib.rs`. The docstring now spells out that
`pointer(Buffer|Uint8Array)` stores only the raw pointer, that inlining
`pointer(Buffer.alloc(...))` or `pointer(_wideStringBuffer(x))` risks
passing a dangling pointer to the native call, and shows the "hoist
the buffer to a named const" pattern the codegen already follows.
Snapshot: all 9 flat enum .d.ts files updated to the new shape;
Apis.d.ts unchanged. No runtime changes.
Gauntlet (green):
- cargo test -p dynwinrt: 96 passed + regression
- cargo test -p dynwinrt-codegen: 15 win32_flat + all other suites
- Node E2Es: taskbarlist, registry, dtm, smtc, flat_registry -> PASS
- repo e2e: py 29/29, ts 28/28
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…urns as .result
Two Copilot-flagged flat-Win32 codegen bugs.
1) meta.rs: parse_flat_apis_from_index silently truncated a method's
argument list when winmd param_defs.len() != sig.types.len(). That
produced a wrapper with the wrong arity — flatInvoke would then
corrupt the callee's stack. Fail loud instead: emit a stderr warning
and skip the whole method. The generated surface then simply lacks
this export, which is far safer than a wrapper that misinvokes.
2) flat.rs: is_status_return treated EVERY I32/U32 return as a Win32
status code. That mis-projected APIs like GetCurrentProcessId ->
u32 (PID) and MulDiv -> i32 (result) as { status: number }.
Now the classification is done at parse time from the RAW winmd
Type (HRESULT / NTSTATUS / LSTATUS) OR from the mapped enum name
(WIN32_ERROR-family, *STATUS-suffixed) — stored on FlatMethodMeta
as
eturn_is_status. Only true status-typedef returns project as
{ status }; plain-integer returns now project as { result }.
Registry snapshot regenerated: RegConnectRegistryExA/W flip from
{ status } to { result } because the win32metadata authors type
their return as raw i32, not LSTATUS. The winmd is the source of
truth for the codegen; the vast majority of Reg* functions (RegCloseKey,
RegOpenKeyExW, etc.) are typed as LSTATUS and still project as
{ status }.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The @returns { ... } JSDoc line for flat exports listed OUT-param fields using the RAW winmd param name (e.g. `lpType`) — but the generated `return { ... }` statement uses the SANITIZED JS identifier (Hungarian-stripped `type`) that `js_param_names_for_method` produces (and which is required to avoid `lpXxx` in the public surface, plus deal with collisions/reserved words). Result: docs said `{ status, lpType }` but callers accessed `r.type`. Fixed by passing the `jnames` list into `describe_return_shape` and indexing by param position — the documented shape now matches the actual return object exactly. Registry snapshot updated: 4 methods (RegEnumValueA/W, RegQueryValueExA/W) flipped the doc from `lpType: <out>` to `type: <out>` — matching what the emitter has always emitted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- flat.rs scalar_slot_write: Handle in-out slots now accept both bigint and Buffer inputs (the .d.ts advertises bigint|Buffer for handles, and BigInt(Buffer) throws). Branch on runtime type: read the u64 out of the buffer when Buffer.isBuffer, otherwise coerce via BigInt. U64 kept as-is because its input surface is bigint|number, both safe for BigInt(). Registry snapshot did not change (no [in,out] pointer-to-Handle params in that namespace). - win32_flat_test.rs: fix misleading doc comment on no_arg_and_void_returns_are_emitted — it tests RegCloseKey(HKEY) which is one [in] param + LSTATUS return, not a void/no-arg export. Renamed comment to match reality. - lib.rs: add DLL loading (SECURITY) section to flat_invoke docstring mirroring the Rust-layer warning. Explains LoadLibraryW search order, DLL preloading/hijacking risk, and the safe patterns (system-DLL name or absolute path). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…] path The E2E preamble said flat-Win32 [DllImport] codegen was `out of scope for this branch`, but this PR ships that codegen (see the new `flat_registry.mjs` E2E that consumes generated wrappers directly). Rewrite the comment to describe the actual relationship: this file is intentionally hand-written for the natural JS surface (`Registry.getString(hive, subKey, valueName)`); the lower-level `[DllImport]` wrappers on `Windows.Win32.System.Registry.Apis` are now generated by `dynwinrt-codegen --lang js --class-name Apis`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot review caught that the flat-Win32 handle typedef advertised
`bigint | Buffer` but the emitter marshals handles via
`DynWinRtValue.pointer(hKey)` — and `pointer(Buffer)` uses the
buffer's own base address, NOT the pointer bits inside it. So a
caller passing a Buffer of pointer bits would end up with the
Buffer's address being interpreted as the HANDLE (i.e., an
address of a stack/heap slot, not the intended kernel handle).
Fix by narrowing the handle type to `bigint | number`:
- `bigint` is the safe path for full 64-bit kernel handles
- `number` is ergonomic for handles that fit in a JS safe int
(small HWND window IDs, etc.)
- `Buffer` is intentionally removed from the surface
Also:
- `wrap_arg_js(Handle)` now emits `pointer(BigInt(hKey))`.
Idempotent for bigint, coerces number, and avoids the
JS Number.MAX_SAFE_INTEGER ambiguity when the caller happens
to hand-write a numeric constant.
- `scalar_slot_write(Handle)` reverts the Buffer branch added
in round 6 — that branch was based on the (now-corrected)
misconception that Buffer was a valid Handle input shape.
Reverted to `BigInt({value_var})` (same as U64), which
handles bigint (identity) and number (coerce) safely.
- Handle typedef doc string explains the ban on Buffer and why.
Registry snapshot regenerated: HKEY/HANDLE/PSECURITY_DESCRIPTOR
types flip from `bigint | Buffer` to `bigint | number` in the
.d.ts (with expanded doc); every `pointer(hKey)` becomes
`pointer(BigInt(hKey))` in the .js. Node E2Es all pass
unchanged because they were already passing bigint HKEY constants.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot review round 9 flagged the doc comments as out of sync with the actual emitter contract: - PWStr/PStr said just "PWSTR/LPCWSTR" / "PSTR/LPCSTR" and "natural surface is string | null". Clarify that the string-input projection is correct for the CONST forms (PCWSTR / PCSTR / LPCWSTR / LPCSTR) which are read-only inputs, and note that the MUTABLE PWSTR/PSTR output-buffer forms flow through the pointer(Buffer) marshalling path in flat.rs — they are NOT string-marshalled. - Handle said "natural surface is bigint | Buffer" — but round 8 fixed the emitter to type handles as bigint | number and explicitly ban Buffer (because pointer(Buffer) uses the buffer's own address, not the pointer bits inside it). Update the comment to match. Doc-only change; no code, no snapshot delta. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`is_status_return_type` (added in round 4) treats HRESULT, NTSTATUS,
and LSTATUS as Win32 status typedefs — but `resolve_named_flat_type`
only had explicit shortcuts for HRESULT and NTSTATUS. Currently harmless
because the win32 metadata models LSTATUS as a plain Int32 typedef that
resolves cleanly through the type-def path, but if a future metadata
revision ever exposed LSTATUS as a `struct { Value: Int32 }` (the
same shape the Handle typedefs use), the TypeDef fallback below would
classify it as `FlatAbiType::Handle` — which routes returns through
`retKind = 'Ptr'` and would mis-marshal the status code as a
pointer address. Fail-loud shortcut: map LSTATUS to I32 up front so
this stays consistent with is_status_return_type.
No snapshot delta (the current metadata already routes LSTATUS
correctly); pure defensive fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… return
Round 11 review fixes for the flat-Win32 codegen:
1. classify(): LPWSTR/LPSTR params marked [out] or [in,out] are
caller-allocated output buffers (RegEnumKeyW, RegEnumValueW,
RegLoadMUIStringW, RegQueryValueW, RegQueryInfoKeyW, ...), not
read-only string inputs. Previously classified as Input, which
fed them through _wideStringBuffer/_narrowStringBuffer — the
flat exports would write into a fresh throwaway buffer that the
caller could never observe. Now routed through OpaquePointer
so the caller supplies (and reads back from) their own Buffer.
2. Argument builder: added an explicit ParamSurface::OpaquePointer
branch that emits DynWinRtValue.pointer(jname) — bypasses
wrap_arg_js, which would still try to allocate a wide/narrow
string buffer if it saw a PWStr/PStr abi. The .d.ts side already
surfaces these as `bigint | Buffer | null`.
3. flat_ret_kind_literal: downgrade the Void => "I32" fallback
to a debug_assert! (matching the existing I64/U64 arm). Void
returns are already filtered out by partition_supported_methods
via unsupported_return_reason (round 6), so the arm is
unreachable; the assert catches a missing upstream filter in
tests instead of silently emitting an I32 wrapper for a void
export.
Snapshot regenerated: 10 Registry APIs (RegEnumKeyA/W, RegEnumKeyExA/W,
RegEnumValueA/W, RegLoadMUIStringA/W, RegQueryInfoKeyA/W,
RegQueryMultipleValuesA/W, RegQueryValueA/W) now expose their [out]
LPWSTR/LPSTR params as `bigint | Buffer | null` instead of
`string | null`. E2E fixture regenerated in lockstep.
Full gauntlet green:
cargo test -p dynwinrt 96 passed + 1 regression
cargo test -p dynwinrt-codegen 15/15 flat + classic + interop + snapshots
5 Node E2Es taskbarlist / registry / dtm / smtc /
flat_registry all PASS
tests/e2e_test.ps1 -SkipBuild py 29/29, ts 28/28
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round 12 review fix: `render_method_dts` was using `dts_type_of` (which
is the *input* param type) for the `result` field of pointer-family
return types. At runtime, however, any `retKind === "Ptr"` — which
`flat_ret_kind_literal` routes for `Ptr`, `PtrTo(_)`, `PWStr`, `PStr`,
and `Handle{..}` — is unconditionally converted through
`_ret.asPointerBigint()`, which returns a plain `bigint` (`0n` for
null). Typing the `.d.ts` `result` as `bigint | Buffer | null` or
`string | null` (as `dts_type_of` did) misdescribed the runtime and
forced callers into wrong-branch narrowing.
Introduces `dts_return_type_of` that maps the pointer family to a
plain `bigint` and delegates everything else to `dts_type_of`. Both
the "no projected out-scalars" and "with projected out-scalars"
branches of the return type synthesis now use it for the `result`
field. Input params and projected out-scalar fields keep using
`dts_type_of` (they still accept caller-supplied Buffers / string
inputs at the boundary).
Registry snapshot regenerated: no delta (Registry APIs return LSTATUS
or void, no pointer-returning exports). Added a synthesized-metadata
unit test `flat_dts_return_types_match_js_runtime` that covers Ptr,
PtrTo, PWStr, PStr, and Handle returns — all must project as
`result: bigint` and the raw `asPointerBigint()` call must appear in
the generated `.js`.
Full gauntlet green:
cargo test -p dynwinrt 96 passed + 1 regression
cargo test -p dynwinrt-codegen 16/16 flat + classic + interop + snapshots
5 Node E2Es taskbarlist / registry / dtm / smtc /
flat_registry all PASS
tests/e2e_test.ps1 -SkipBuild py 29/29, ts 28/28
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ollisions
Round 13 review fix: `collect_enum` was keying its dedup `HashSet` on
the enum's *simple* name only, even though `TypeMeta::Enum` carries
both `namespace` and `name`. If an `Apis` class ever referenced two
distinct enums that shared a simple name across different namespaces
(e.g. `Ns.A::Status` vs `Ns.B::Status`), the second one was silently
dropped at parse time — the emitted `Apis.d.ts` would then reference
the wrong enum type, and the sibling `Status.js`/`Status.d.ts` files
would carry only the first variant's members.
Two-part fix:
1. `parse_flat_apis_from_index`: change the dedup key from
`HashSet<String>` (simple name) to `HashSet<(String, String)>`
(namespace, name). Both distinct enums now reach codegen.
2. `generate_flat_apis_files`: enum sibling files (`Foo.js`,
`Foo.d.ts`) still use the simple name for the file name and
`Apis.d.ts` imports, so a genuine collision would corrupt the
emitted module. Added a fail-loud check that panics with a clear
diagnostic (`multiple distinct enums named X referenced by C from
namespaces [...]`) instead of silently emitting a wrong-shape
module. When we need to support this shape, the fix is to add
namespace-qualified aliasing in the emitter — the panic points
directly at that decision.
Registry snapshot regenerated: no delta (Registry references
WIN32_ERROR, REG_ROUTINE_FLAGS, REG_KEY_ACCESS_RIGHTS, REG_SAVE_FORMAT,
REG_VALUE_TYPE, and their close relatives — all uniquely named).
Node E2E fixture regenerated in lockstep.
Added regression test `flat_fails_loud_on_simple_name_enum_collision`
that constructs a `FlatApisMeta` with two `Status` enums in different
namespaces and asserts the emitter panics.
Full gauntlet green:
cargo test -p dynwinrt 96 passed + 1 regression
cargo test -p dynwinrt-codegen 17/17 flat + classic + interop + snapshots
5 Node E2Es taskbarlist / registry / dtm / smtc /
flat_registry all PASS
tests/e2e_test.ps1 -SkipBuild py 29/29, ts 28/28
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gin/main via classic)
…+ COM pointer adoption
Fixes 4 gaps surfaced by a systematic Windows.Win32 winmd exploration sweep:
- C4: add napi DynWinRtType.u16Type() (+ i16Type/u8Type/f32Type/f64Type aliases).
- C1: [in] HRESULT params project as number / i32Type() / DynWinRtValue.i32(hr).
- C2: caller-owned [out] PWSTR + cch string buffers (IShellLinkW.GetPath/
GetDescription) generate a real wrapper via ParamDirection::OutStringBuffer;
narrow detector (direct PWSTR/PSTR + adjacent char-count; PWSTR* not matched;
cb byte-counts excluded for PWSTR; PSTR fails loud). Previously crashed.
- C3: adoptComPointer(ptr, iid?) adopts an AddRef-owned returned COM pointer via
IUnknown::from_raw (no extra AddRef) + optional QI-validate; codegen wraps
directly-named TypeMeta::Interface out-params as typed wrappers.
Tests: TDD unit tests each; refcount-correct native adoption test;
e2e/shelllink-buffer.mjs proves SetPath/GetPath + SetDescription/GetDescription
round-trips on live classic COM.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…64/f32/f64/pointer)
Fixes flat return-value gaps surfaced by the Windows.Win32 winmd exploration sweep,
which previously skipped ~945 exports and truncated pointer returns:
- F4: function-pointer returns (FARPROC/PROC/NEARPROC + Delegate typedefs) now
classify as pointer-width and decode as BigInt via asPointerBigint() instead
of truncating through I32 (GetProcAddress returned a 32-bit-truncated value).
Unclassified returns now FAIL LOUD (skipped) rather than silently I32.
- void returns: FlatReturnKind::Void via libffi Type::void(); generated JS returns
undefined (or the projected-outs object) — unblocks ~899 exports incl.
GetNativeSystemInfo/GetSystemInfo out-struct fills.
- i64/u64 returns: libffi Type::i64()/u64(); decode via new toI64BigInt()/
toU64BigInt() (BigInt, no JS-number truncation) — unblocks GetTickCount64 etc.
- f32/f64 returns AND args: libffi Type::f32()/f64() (Win64 XMM0 ABI); toF64 decode.
Every FlatReturnKind pairs the matching libffi Type with the corresponding
cif.call::<T>(); the retKind strings, JS decoders, and .d.ts types line up at every
site (both match arms are exhaustive — a new kind fails to compile, not truncates).
Tests: rewrote the former skip-tests to assert emitted retKinds/decoders; added an
unknown-return fail-loud test and a libffi-level Rust unit test for the f64/u64/void
return paths (authoritative, export-independent). e2e/flat_returns.mjs proves live:
un-truncated GetProcAddress pointer, GetTickCount64 (u64), GetNativeSystemInfo (void
out-struct), and D2D1Tan (f32 + float arg).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…untime read Copilot review (PR #2): projected out/in-out slot return fields used dts_type_of on the pointee, so a handle out-slot (e.g. phkResult: HKEY) was typed as the handle alias (bigint | number). But the generated .js reads handle slots with readBigUInt64LE, always producing a bigint. Use dts_return_type_of for the returned field so the .d.ts matches the runtime contract (handles/pointers -> bigint; scalars -> number; enums -> alias). Input param types are unchanged. Snapshot updated.
… opaque pointer) After adding param/return validation, bare Unknown by-value params/returns are skipped fail-loud rather than emitted as opaque pointers; only PtrTo(Unknown) is kept as an opaque pointer param. Update the doc comment to match.
…o flat-Win32 Integrates Leilei Zhang's "isolate classic COM from WinRT" refactor (com module + napi DynCom/DynComMethodSig + com_metadata codegen) with the flat-Win32 vertical. Conflict resolution: - bindings/js/src/lib.rs: keep the shared native-ABI + flat methods on DynWinRTValue (pointer, asPointerBigint, toI64BigInt, toU64BigInt, flatInvoke, flatLastError, value ctors). The truly classic-COM methods (coCreateInstance, adoptComPointer, registerInterfaceUnknown, createTestHwnd) now live on DynCom (com.rs). - meta.rs: keep flat metadata (FlatAbiType, parse_flat_apis, type mapping); take the classic-metadata removal (moved to com_metadata). - main.rs: keep both generation paths — com_metadata (classic) and flat Apis. - crates/dynwinrt/src/lib.rs: keep both `pub mod com;` and `pub mod flat_call;`. - Classic e2e wrappers + interop tests: take Leilei's regenerated DynCom versions. Verified: cargo build + dynwinrt (116) + codegen suites green; napi rebuilt; all 7 Win32 e2e (taskbarlist, dtm, smtc, shelllink-buffer, flat_registry, registry, flat_returns) pass. Independently re-verified (no lost flat method, no orphaned classic call, no leftover conflict markers). Co-authored-by: Leilei Zhang <leilzh@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ange, enum signedness) An independent multi-agent self-review (beyond the Copilot loop) found issues the passing E2E sweep did not exercise: - CRITICAL (memory safety): DynWinRtValue.pointer(existingObject) returned an UNOWNED raw pointer; if adopted via DynCom.adoptComPointer it would take ownership and double-free/UAF an object the original JS wrapper still holds. pointer() now rejects Object/DynWinRtValue inputs (raw BigInt/number/Buffer/null only). No committed caller passed an object (verified). Adds pointer-u64-safety.mjs. - HIGH (correctness): DynWinRtValue.u64(value: i64) could not represent u64 values above i64::MAX (lossy/rejected). Now takes a lossless BigInt validated as a full u64; test covers 0xFFFFFFFFFFFFFFFF. - MEDIUM: flat enum underlying was always emitted I32, so real unsigned Win32 enums lost their signedness and the unsigned-enum >>>0 u32-boundary coercion was dead code. Now preserves the enum backing (value__) signedness; snapshot updated; test added. - TEST rigor: flat_returns.mjs now reports a visible SKIP for the float live check when Direct2D is unavailable instead of implying the float path was proven. Verified: cargo dynwinrt 116, codegen 140; napi rebuilt; all 8 Win32 e2e pass (classic via DynCom, flat via DynWinRtValue) with no regression. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…es plain numbers) The prior self-review hardening changed DynWinRtValue.u64 to require a BigInt, which broke the WinRT javascript codegen path — it emits DynWinRtValue.u64(<number>) without a BigInt wrapper (e.g. async_memory_stream_roundtrip stream sizes), so a plain number was rejected (CI e2e ts 27/28). Accept Either<BigInt, i64>: a JS number converts via the i64 branch (validated non-negative), a JS bigint via the BigInt branch (lossless full unsigned-64 range). Restores WinRT compatibility while keeping the > i64::MAX support from the u64 fix. Verified: WinRT pipeline py 34/34 + ts 28/28 (async_memory_stream_roundtrip passes), all 8 Win32 e2e pass, u64 full-range safety test passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…m params/returns Copilot review (follow-up to preserving enum underlying signedness): now that enum underlying width/signedness is captured (incl I64/U64/F32/F64 via value__), the rest of the enum model still can't faithfully represent 64-bit/float enums — EnumMember.value is i32-backed and the TS surface projects enums as number-based unions. Emitting such a wrapper would produce truncated/wrong member constants and an ABI-mismatched calling convention. Add a shared enum_underlying_unrepresentable() helper and skip (fail-loud, with a warning) any method whose return OR param (by value, or PtrTo out-param) is an enum with a non-32-bit-integer underlying. Representable I8/U8/I16/U16/I32/U32 enums are unaffected. Adds a regression test (U64-underlying enum param skipped; U32 kept). Registry snapshot unchanged (no registry export uses a 64-bit enum). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nsferManager substring
The interop return-type assertion matched the bare substring "DataTransferManager",
which is always present as part of the interop class name `IDataTransferManagerInterop`.
That made it a false positive: it passed regardless of the real return type and its
message ("must project the return type as DataTransferManager") contradicted the actual
design, which returns the explicit `DynWinRtValue` bridge (no synthesized WinRT
runtime-class projection). Assert the real contract instead:
`getForWindow(appWindow: HWND): DynWinRtValue;`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gordon Lam (yeelam-gordon)
marked this pull request as ready for review
July 23, 2026 23:33
The fallback error hardcoded "CoTaskMem-allocated", but take_raw_pointer serves
multiple pointer kinds (description = "COM interface", "wide-string",
"ANSI-string", "CoTaskMem allocation"). The claim was inaccurate for 3 of 4
callers. The `description` parameter already conveys the kind, so drop the
misleading qualifier: "Expected a {description} raw pointer".
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… assertions
1. Address review feedback: WIN32_WINMD was hardcoded to a developer-local path
(C:\s\win32metadata\Windows.Win32.winmd), so the whole classic-COM test suite
silently self-skipped on CI and other machines. Replace the const with a
win32_winmd() helper that honors the DYNWINRT_WIN32_WINMD environment variable
and falls back to the local path. Applied to win32_com_test.rs and
win32_com_interop_test.rs.
2. Because those tests never ran in CI, two assertions went stale after the
"isolate classic COM from WinRT" refactor and were failing locally:
- shellitem_getdisplayname_...: expected DynWinRtMethodSig/DynWinRtType.i32Type()
- u16_input_param_...: expected DynWinRtValue.u16(wHotkey)
Classic-COM codegen now emits DynComMethodSig / DynCom.i32Type() / DynCom.u16(...).
Update the expectations (intent unchanged: callee-allocated addOut(pointer), and
u16 wrapped via the existing ctor). Full dynwinrt-codegen suite now green.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-back iid_pointer Two classic-COM memory fixes surfaced by the pointer-lifecycle audit: #2 (Medium, double-release hazard): DynCom.pointer() accepted an existing DynWinRtValue and, for Object values, returned a borrowed raw COM pointer owner-backed by a clone. That raw pointer is indistinguishable from an owned +1 pointer to adoptComPointer(), enabling a double-release. Align tier1 with tier2: reject all DynWinRtValue inputs; callers pass raw pointer bits, Buffer/Uint8Array, or null. Generated COM code only ever passes HWND/buffer/ PIDL values to pointer(), never objects, so nothing breaks. Adds e2e/pointer-reject-object.mjs regression. #4 (Low, unbounded leak): iid_pointer boxed one GUID per distinct GUID into a static HashMap and never freed it. Replace with an owner-backed NativePointerOwner::Guid(Box<GUID>) that frees on drop/GC. The REFIID is only read during the synchronous COM call and the JS temporary outlives it. Classic-COM gauntlet (taskbarlist, dtm, smtc, shelllink, hwnd) all pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Brings the latest classic-COM fixes into the combined branch: - #2 pointer() rejects DynWinRtValue inputs (double-release hazard) - #4 iid_pointer owner-backed GUID (no static leak) - env-overridable DYNWINRT_WIN32_WINMD test path + stale-assertion fixes - take_raw_pointer ownership-neutral message; interop return-type assertion Conflict resolved in win32_com_test.rs (comment wording only; both sides already assert the DynCom.u16(...) contract).
…ngling (#1) The flat-Win32 path did LoadLibraryW + FreeLibrary per call (LoadedLibrary RAII). A flat export returning a pointer/string/function address INTO the module (FlatReturnKind::Ptr, PWSTR/PSTR/Handle) would dangle once FreeLibrary ran before the caller used it. Masked today only because kernel32/advapi32 are always resident. Replace the per-call load/free with a process-lifetime module cache (load once, never FreeLibrary), matching .NET [DllImport] behavior and removing per-call load/unload overhead. Adds a regression test proving the cached handle is stable and a Ptr-returning export (GetCommandLineW) stays valid after the call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…er passing Addresses two Copilot review threads on the module-cache change: - flatInvoke JSDoc still described per-call FreeLibrary and warned that 'Ptr' returns may dangle. That is now false: modules are cached process-lifetime and never unloaded, so Ptr returns into a module stay valid. Rewrote the doc. - Handle arguments were wrapped as DynWinRtValue.pointer(BigInt(x)). For a JS number above Number.MAX_SAFE_INTEGER the bits are already lost before BigInt sees them, and the wrap bypassed pointer()'s safe-integer validation. Pass the value straight through: pointer() accepts bigint|number, carries a bigint losslessly, and rejects unsafe numbers instead of silently truncating. Regenerated the registry Apis.js golden snapshot to match. (The third thread — "unused use napi::JsValue" — is a false positive: the trait provides the .value() method used on Unknown; removing it fails to compile.) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ison recovery Second review round on the flat/combined branch: - DynWinRtValue.u64() number branch took the JS number as i64 then cast to u64, silently rounding/truncating fractional or out-of-safe-range numbers into a wrong value. Switch the numeric arm to f64 and validate a finite, non-negative safe integer (reject otherwise; callers use a bigint for values above 2^53-1). The bigint path is unchanged (full lossless u64 range). WinRT py/ts pipeline (34/34, 28/28) still green. - pointer() doc claimed classic-COM only; it is also the primary way to pass pointers/buffers into flatInvoke. Doc updated. - flat module cache used lock().unwrap(), which would abort the host process if the mutex was ever poisoned by an unrelated panic. Recover the map from a poisoned mutex instead (append-only name->HMODULE map, safe to reuse). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…; reserve `result` - get_cached_module held the module-cache mutex across LoadLibraryW. LoadLibraryW runs loader work / the DLL's DllMain, which can re-enter flat_invoke and lock the same (non-reentrant) mutex → deadlock, and it serialized all concurrent flat calls during a load. Restructured to a double-checked pattern: probe the cache under a short lock, release it, LoadLibraryW without the lock, then re-acquire to insert (first writer wins). - The flat reserved-name guard covered `status` but not `result`; both are return-object field names, so a parameter/out-field stripping to `result` would create a duplicate `result:` key and overwrite the return value. Reserve `result` too. Adds a js_param_name unit test covering status/result/keywords. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The flat-Win32 test suite hard-coded C:\s\win32metadata\Windows.Win32.winmd, so it silently self-skipped on CI/other machines even when win32metadata was present elsewhere. Mirror the COM test suites: resolve the path via a win32_winmd() helper that honors the DYNWINRT_WIN32_WINMD environment variable and falls back to the local checkout path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… helper) scalar_slot_write emitted `writeBigUInt64LE(BigInt(x))` for handle in/out slots. Handles are typed `bigint | number`, so a JS number above 2^53-1 has already lost bits before BigInt sees it, silently writing a wrong handle — the same lossy-number class fixed on the handle ARG path. Route handle slot writes through a new `_handleU64` helper that carries a bigint losslessly and rejects a number that isn't a non-negative safe integer. The helper is emitted only when a generated file actually writes a handle slot. (I64/U64 slots are `bigint`-typed, so they keep the direct BigInt() coercion.) Adds a unit test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Backfill dedicated regression tests for fixes that shipped without one: - handle_arg_passes_through_without_lossy_bigint_wrap (16d293f): asserts a handle ARG emits `DynWinRtValue.pointer(x)`, not `pointer(BigInt(x))`. - e2e/u64-validation.mjs (123d172): asserts u64() rejects fractional/negative/ unsafe-integer/NaN/Infinity numbers and overflow bigints, and accepts full unsigned-64 bigints — the number branch previously truncated silently. - module_cache_recovers_from_poisoned_mutex (123d172): poisons the cache mutex and asserts get_cached_module still succeeds (old lock().unwrap() panicked). - concurrent_first_load_does_not_deadlock (98b3f99): best-effort concurrency smoke test for the lock-not-held-during-LoadLibraryW change (the exact DllMain re-entrancy deadlock isn't deterministically reproducible in a unit test). The first three fail against the pre-fix code; the last is a concurrency smoke. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Classic COM handle-value newtypes like HWND were projected as bigint | Buffer, but DynCom.pointer(Buffer) passes the Buffer's address instead of the handle bits it contains. Emit handle values as bigint | number while keeping NUL-terminated string pointer aliases such as PWSTR as bigint | Buffer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Backfills the missing test for the #4 memory fix (commit 50193b4): asserts iid_pointer returns an owner-backed DynWinRtValue (NativePointerOwner::Guid, so the boxed GUID frees on drop) holding the correct GUID bytes, and that two concurrently-live calls for the same GUID allocate distinct boxes (no shared static cache). Verified this FAILS against the pre-fix static-cache-leak impl (the owner-backed assertion fails) and passes after. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Windows DLL resolution is case-insensitive, but get_cached_module used the raw `dll` string as the cache key, so `ADVAPI32.dll` and `advapi32.dll` created two cache entries and two LoadLibraryW references for the same module. Normalize the key with to_ascii_lowercase(). Adds module_cache_key_is_case_insensitive, which fails against the raw-string key (the case variant added a second entry). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Opaque pointer params were typed `bigint | Buffer | null`, but the runtime DynWinRtValue.pointer() also accepts a Uint8Array (uses its data pointer). The narrower type made a valid Uint8Array argument a spurious TypeScript error. Widen to `bigint | Buffer | Uint8Array | null`. Adds opaque_pointer_param_dts_accepts_uint8array (fails against the old narrower type) and regenerates the registry Apis.d.ts golden. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…list The pointer() doc comment's "Accepts:" list omitted `number` and `Uint8Array` even though both are accepted (and advertised in ts_arg_type). Doc-only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the current main WinRT and Classic COM architecture, port the flat Win32 delta, and move flat E2E coverage into the unified test layout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41a826d4-b04a-4932-b1b8-31173fc5ab40
Integrate mixed COM packaging and the latest Python and WinUI codegen updates while retaining the flat Win32 routing for subsequent review repairs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41a826d4-b04a-4932-b1b8-31173fc5ab40
Bring in Python WinUI E2E and DispatcherQueue GIL integration from PR microsoft#79 before continuing the uncommitted flat Win32 review repairs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41a826d4-b04a-4932-b1b8-31173fc5ab40
Add a dedicated /win32 runtime and codegen domain, preserve native metadata contracts, fail closed on unsafe ABI shapes, secure system DLL loading, and integrate namespace-safe packaging and E2E coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41a826d4-b04a-4932-b1b8-31173fc5ab40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds flat Win32 (
[DllImport]) support to dynwinrt: the code generator now discovers plain C exports inWindows.Win32.winmd(static methods onApisclasses) and emits natural TS/JS wrappers, backed by a generalLoadLibrary+GetProcAddress+ libffi runtime path. Demonstrated end‑to‑end with a generated Registry wrapper that reads real values on live Windows.What you write (generated API)
Motivation
Classic COM is vtable/object‑based; flat Win32 APIs (
RegOpenKeyExW,CreateFileW,CredReadW,GetForegroundWindow, …) are plain DLL exports — a fundamentally different invocation path (no object, no vtable). A large share of the Electron native modules people ship today is exactly this cohort (registry, credential, window/input access), so covering it is what makes dynwinrt relevant to those apps.Architecture
LPCWSTR↔string,[out]pointer params → return values,HANDLE/HKEY→bigint, caller‑allocated output buffers + in/out size (LPDWORD), UTF‑16 decode,LSTATUS/Win32 error surfaced (0 = success; non‑zero throws).I64/F32/F64returns) — it skips/erroring at generation time rather than silently emitting a wrong‑value wrapper.Developer experience (sample usage)
High‑level generated wrapper:
The underlying generated exports are also emitted (typed,
flatInvokehidden):Generate — partial, per namespace/class:
Tests
cargo test -p dynwinrtflat_invokescalar/pointer/buffer marshalling; 5 live‑advapi32registry tests (out‑handle viaPHKEY, caller buffer, in/outLPDWORDsize,ERROR_MORE_DATA, null‑size query) → 96 passed + regressioncargo test -p dynwinrt-codegen[DllImport]discovery, natural emit,[out]→return, fail‑loud unsupported returns, deterministic snapshot — TDD (6 unit + 10 integration, red→green)flat_registry.mjsreads a realProductNamevia the generated wrapper (plus corner cases: missing value/subkey → typed error)Combined with #65, the full stack was verified together: cargo (96 + regression), codegen, 5 Node E2Es (taskbar/registry/dtm/smtc/flat_registry), WinRT pipeline py 29/29 · ts 28/28. An automated code‑review loop was run to convergence (buffer keep‑alive/lifetime, PWSTR/PSTR handling, fail‑loud, enum dedup).
Scope / follow‑on
The registry vertical proves the mechanism. Broader flat‑API coverage — by‑value structs, arrays, other DLLs,
PWSTR‑out →stringmarshal‑back, callbacks/function pointers, ANSI/W auto‑preference — is follow‑on work; unsupported shapes fail loud today, so nothing is silently wrong.The flat layer covers the common scalar / string-in / enum / handle /
[out]-scalar path cleanly. The harder aggregate/callback cases are not yet covered and fail loud (skip + warning) — never silently wrong:void*/ mutablePWSTR-out — emitted as a rawBuffer | nullslot the caller must allocate and pack (e.g.RegQueryValueExWlpData). The truly dynamic-size case is inherent to the Win32 contract.EnumWindows, hooks) — opaquePtr; no JS-function -> native thunk, so these are effectively uncallable today.Measured skip rates on real
Windows.Win32.winmd(--class-name Apis):SCard*; every Credential Manager fn (credReadW/WriteW/EnumerateW/DeleteW/Free) generatesSkips concentrate in struct-heavy niche sub-families, not mainstream functions; nothing is silently wrong (kept = correct, skipped = warned).
Handle ergonomics (#4): handle-value params (HWND / HANDLE / HKEY) accept
bigint | numberand work today, but an ElectrongetNativeWindowHandle()Buffer must be unwrapped to a value first (e.g..readBigUInt64LE(0)). A codegen improvement that accepts the Buffer directly — reading its little-endian bytes as the handle VALUE — is prepared and held pending the foundational rework in #65 (to avoid churn in the same projection layer).Foundational classic-COM ABI issues (caller-owned buffers, namespace-mode vtable slots,
ISize/USizewidth, pointer-ownership double-adopt, typed-array detachment, BSTR/enum, packaging) are tracked in #65 (see leileizhang (@lei9444)'s review) and apply to the classic-COM half this PR stacks on.