Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 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
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
bc0c8bb
Classic-COM codegen fixes: u16Type + HRESULT-in + out-string buffers …
yeelam-gordon Jul 23, 2026
a4a26c2
Fix u16/i16 arg-wrapper codegen: emit DynWinRtValue.u16()/i16() not *…
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
fdb9391
Fix IMapView\2 parameterized IID constant (was inconsistent with code…
yeelam-gordon Jul 23, 2026
73f09f1
Classic-COM codegen: project scalar [out] pointer params as scalar ou…
yeelam-gordon Jul 23, 2026
459ebc9
refactor(com): isolate classic COM from WinRT
lei9444 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
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
f8dc42d
Merge origin/main into feat/win32-com-tier1
lei9444 Jul 28, 2026
9ecbdaf
feat(codegen/com): accept Electron Buffer for handle-value args (no m…
yeelam-gordon Jul 28, 2026
3e6d98e
Harden Classic COM generation and runtime safety
lei9444 Jul 28, 2026
775f0d3
Isolate Classic COM runtime entrypoint
lei9444 Jul 28, 2026
951d6f6
Document Classic COM support and demand
lei9444 Jul 29, 2026
290770f
Merge remote handle-buffer support safely
lei9444 Jul 29, 2026
bbecf47
Document Classic COM ABI development rules
lei9444 Jul 29, 2026
96c459e
Separate WinRT and COM call planners
lei9444 Jul 29, 2026
c926148
Harden Classic COM type semantics
lei9444 Jul 30, 2026
4d64b5d
Separate WinRT and COM codegen domains
lei9444 Jul 30, 2026
0d537f3
Merge origin/main into PR 65
lei9444 Jul 30, 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
7 changes: 5 additions & 2 deletions .github/agents/e2e-test.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: e2e-test
description: Run end-to-end tests for dynwinrt code generation and WinRT API invocation
description: Run end-to-end tests for dynwinrt code generation and WinRT/Classic COM API invocation
tools:
- powershell
- view
Expand All @@ -21,6 +21,9 @@ You run and manage the dynwinrt end-to-end test suite.
# Python only
.\tests\e2e_test.ps1 -SkipBuild -Lang py

# Classic COM only (requires Windows.Win32.winmd)
.\tests\e2e_test.ps1 -SkipBuild -Lang com

# Full build + test
.\tests\e2e_test.ps1
```
Expand All @@ -40,5 +43,5 @@ Avoid APIs that need WinAppSDK, network, or user interaction.
## Diagnosing failures

1. Check `tests/e2e_generated/results_py.json` or `results_ts.json` for structured failure details
2. Inspect generated code in `tests/e2e_generated/python_bindings/` or `ts/`
2. Inspect generated code in `tests/e2e_generated/python_bindings/`, `ts/`, or `com/`
3. Common issues: circular imports in codegen, naming mismatch (Python snake_case vs TS camelCase)
17 changes: 14 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ python -m pytest tests/ -v
# JS binding (requires Node.js 18+)
cd bindings/js
npm install
npx napi build --no-const-enum --platform --release -o dist
npm run build

# Code generation (JS + .d.ts is the default; --lang py for Python)
cargo run -p dynwinrt-codegen -- generate --namespace Windows.Foundation --class-name Uri --output ./generated
Expand All @@ -53,9 +53,9 @@ The E2E test framework validates the full pipeline: reading .winmd metadata →
- `instantiate`: how to create an instance (`activate`, `static_factory`, or `none`)
- `checks`: array of assertions (`property_equals`, `property_exists`, `method_equals`, `method_result_contains`, `static_equals`, `static_not_null`)

2. **Runners** (`tests/runners/py_runner.py`, `tests/runners/ts_runner.ts`) read the specs and execute them, outputting `results.json`.
2. **Runners** (`tests/runners/py_runner.py`, `tests/runners/ts_runner.ts`, and `tests/runners/com/*.mjs`) execute generated WinRT and Classic COM bindings.

3. **Orchestrator** (`tests/e2e_test.ps1`) handles build, code generation, and runner invocation.
3. **Orchestrator** (`tests/e2e_test.ps1`) handles build, temporary code generation, and runner invocation. Use `-Lang com` for the Classic COM suite; it requires `DYNWINRT_WIN32_WINMD` or an installed `Microsoft.Windows.SDK.Win32Metadata` package.

4. **Adding new test cases**: Add entries to `e2e_specs.json`:
```json
Expand Down Expand Up @@ -95,6 +95,17 @@ These APIs are available on any Windows 10/11 machine without WinAppSDK:
- **Method invocation** returns a single `WinRTValue` (not a list) in Python binding
- **Generated code** uses relative imports (`from .module import Class`) — must be in a Python package

### Classic COM implementation rule

For Classic COM, Windows.Win32 metadata, pointer, handle, ownership, or native
ABI changes, follow
[`classic-com-abi`](skills/classic-com-abi/SKILL.md). Model native type plus
parameter contract before language projection, keep COM separate from WinRT,
and fail closed when layout or ownership is incomplete. JavaScript ergonomics
belong to the codegen projection layer; the renderer must not infer ABI
semantics. Classic COM changes must preserve existing WinRT models, generated
output, runtime behavior, and the `@microsoft/dynwinrt` root API.

### Code Generator (dynwinrt-codegen)
- `src/codegen/project.rs` + `src/codegen/projected.rs` — Build the language-neutral `ProjectedFile` IR from parsed metadata
- `src/codegen/render_js.rs` + `src/codegen/render_dts.rs` — Render IR to `.js` and `.d.ts`
Expand Down
307 changes: 307 additions & 0 deletions .github/skills/classic-com-abi/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
---
name: classic-com-abi
description: Use when implementing or reviewing Classic COM, Windows.Win32.winmd, native ABI, pointer, handle, ownership, libffi, or COM codegen changes in dynwinrt.
---

# Classic COM ABI development

Use this skill for changes under:

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

Read [`docs/classic-com-support.md`](../../../docs/classic-com-support.md)
before changing supported types or claiming support for an interface.

## Core principle

Start from the native ABI type **and parameter contract**, never from the
desired JavaScript/Python representation.

```text
Windows.Win32.winmd facts
-> COM-local semantic ABI model
-> validation and ownership plan
-> libffi call plan
-> language projection
```

`Buffer`, `bigint`, `string`, and generated wrappers are projection choices.
They must not determine native semantics.

## Required semantic model

Preserve these facts before rendering:

- native type name and underlying type;
- pointer depth;
- const/mutability;
- `In`, `Out`, or `InOut`;
- nullable/required state;
- struct/union size, alignment, packing, and fields;
- count/capacity/actual-length parameter relationships;
- ownership transfer;
- allocator or cleanup function;
- interface IID and reference ownership; and
- return convention: HRESULT, semantic HRESULT, direct value, pointer, or
`void`.

Do not erase these facts into a generic `Object` or pointer before validation.

## Semantic categories

Model at least these categories explicitly:

```text
Scalar
Enum
NativeStruct
NativeUnion
HandleValue
DataPointer
StringPointer
Bstr
ComInterface
CountedBuffer
SafeArray
Variant
FunctionPointer
Unknown
```

Unknown or incomplete categories must fail closed.

## Layer boundaries

1. Keep Classic COM metadata and projected types COM-local.
2. Do not add Classic COM concepts to the existing WinRT metadata model or
`DynWinRt*` public surface.
3. Sharing private libffi storage and vtable dispatch is allowed.
4. Keep the npm root WinRT-only; generated COM bindings import
`@microsoft/dynwinrt/com`.
5. Renderers consume validated semantic IR. They must not infer ABI semantics
from names, JavaScript values, or struct shape.

### Required runtime architecture

```text
WinRT metadata -> signature.rs (WinRT planner) --------\
-> native_call.rs -> call.rs -> native method
COM metadata -> com.rs (COM planner and method table) /
```

Keep these source-level responsibilities distinct:

| Component | Required responsibility |
|---|---|
| `signature.rs` | WinRT-only signature facade preserving existing `In`, `Out`, fill-array, HRESULT, and out-value behavior. |
| `com.rs` | COM-local `Type`, `MethodSignature`, `Interface`, `MethodHandle`, interface roots, method registry, pointer/InOut semantics, and native return conventions. |
| `native_call.rs` | Private lowering backend for completed signatures: parameter/output indexing, value validation and coercion, array ABI expansion, fast-path selection, libffi CIF preparation, and result coordination. |
| `call.rs` | Private executor: vtable lookup, stable ABI storage, libffi argument construction and invocation, and decoding raw output slots according to the plan. |

Apply these rules:

- WinRT methods stay in the WinRT `MetadataTable`; COM methods stay in the
COM-local registry.
- Only the WinRT planner may define WinRT signature behavior. Do not add raw
pointers, `InOut`, direct native returns, or `void` returns to its public
model.
- Only the COM metadata/projection and planner layers may interpret pointer
categories, parameter direction, return convention, and ownership.
- A by-value GUID is not REFIID. Dynamic-IID output adoption requires
pointer-shaped metadata plus an explicit `iid`/`riid` semantic parameter.
- `native_call.rs` may validate and lower an already-described call, but must
not infer metadata semantics, allocator ownership, or language projection.
- `call.rs` must execute the completed plan without inferring metadata,
ownership, or projection contracts from the caller or language-level value.
- Native methods published through a shared registry must be fully constructed
and immutable. Any manual `Send`/`Sync` implementation requires a documented
libffi read-only safety argument and compile-time trait tests.
- Exact identity checks are required for structs. Preserve established
ABI-compatible WinRT projection aliases such as Char16/U16 and enum/I32
arrays.

### Required codegen architecture

```text
ComInterfaceMeta
-> codegen/com/project
-> validated ComType / ProjectedComMethod
-> codegen/com/javascript renderer
```

- Keep WinRT generators under `codegen/winrt`; do not import COM semantic IR
into them.
- Convert shared `TypeMeta` values to the closed COM-local `ComType` set before
rendering.
- Encode parameter direction, native return convention, result ordering,
ownership, cleanup, string-buffer relationships, activation, and
dynamic-IID behavior in `ProjectedComMethod`.
- Production renderers may consume only projected COM IR. They must not import
`TypeMeta`, `MethodMeta`, `ParamMeta`, metadata attributes, or infer
ownership.
- Every renderer match over `ComType` must be exhaustive. Never use a wildcard
branch that emits `pointerType`, `Buffer`, bigint, or a raw value.
- Transparent scalar typedefs preserve their underlying scalar ABI. A
one-field Win32 struct is not automatically a handle.
- Pointer aliases require explicit `HandleValue`, `DataPointer`, or
`StringPointer` classification. Unknown aliases fail closed.
- Cleanup identifiers must match a single known allocator exactly. Do not use
substring matching.

## Projection responsibility

Keep these responsibilities separate:

| Layer | Responsibility |
|---|---|
| Runtime / ABI | Faithfully and safely execute a fully described native call: storage, libffi types, vtable dispatch, HRESULT, ownership, and cleanup. |
| Codegen semantic projection | Turn validated COM semantics into an idiomatic language API: Buffer/string/bigint choices, camelCase, overloads, optional arguments, hidden ABI parameters, and projected return values. |
| Renderer | Serialize the projection decision into JavaScript and declarations. It must not discover or guess native semantics. |

Electron/Node conveniences belong in the JavaScript projection. The runtime may
provide a small, centralized safety primitive such as `handleValue()`, but it
must not decide that an arbitrary Buffer represents a handle.

## WinRT compatibility invariant

Classic COM work must not change existing WinRT semantics.

- Do not add COM-only types, directions, ownership, pointers, or return
conventions to the public WinRT model.
- Do not change existing `DynWinRt*` behavior or the
`@microsoft/dynwinrt` root surface.
- Do not change generated WinRT constructors, method signatures, imports,
naming, ownership, or output files as a side effect of COM support.
- Shared ABI/libffi helpers must remain private and behavior-neutral for WinRT.
- Route Classic COM through COM-local metadata and projection before any
language renderer.
- Require WinRT snapshot, package, runtime, and live E2E regression coverage
for every shared-infrastructure change.

## Pointer and Buffer rules

A Node Buffer can have different native meanings:

| Semantic type | Buffer meaning | Projection |
|---|---|---|
| Handle value | Pointer-width bytes containing a numeric handle | Explicit `DynCom.handleValue()` |
| Data pointer | Native data stored in the Buffer | `DynCom.pointer(buffer)` passes and retains its address |
| String pointer | Encoded, terminated string bytes | Pass the backing address with encoding validation |
| BSTR | Length-prefixed Automation allocation | Dedicated BSTR allocation/conversion |
| COM interface | Reference-counted interface pointer | Managed COM wrapper, never a Buffer |

Never apply one Buffer interpretation to every pointer-shaped typedef.

For Electron HWND input:

- accept Buffer/Uint8Array only for a confirmed `HWND` input;
- require exactly `size_of::<usize>()` bytes;
- decode little-endian handle bits in the centralized runtime helper;
- keep HWND output aliases numeric; and
- keep PSID, security descriptors, structs, and strings on address semantics.

Do not infer `HandleValue` merely because a Win32 struct has one `Value`
pointer field. Use metadata attributes and an explicit conservative mapping.
Examples:

- `HANDLE`: `RAIIFree(CloseHandle)`;
- `HKEY`: `RAIIFree(RegCloseKey)`;
- `HICON`: `RAIIFree(DestroyIcon)`;
- `HWND`: `AlsoUsableFor(HANDLE)`;
- `BSTR`: `RAIIFree(SysFreeString)`;
- `PSID`: data pointer, not a handle value.

## Ownership rules

- `CoCreateInstance`, QueryInterface, and typed interface out-parameters return
owned `+1` references.
- Managed COM values release automatically; explicit `release()` is only
deterministic early release.
- Interface inputs are borrowed unless the callee AddRefs them for retention.
- `adoptComPointer()` accepts only a native output known to transfer `+1`.
- Numeric and Buffer-backed pointers are borrowed and cannot be adopted.
- Pair BSTR with `SysFreeString`.
- Pair HSTRING ownership with `WindowsDeleteString`; never project HSTRING as a
numeric pointer.
- Pair CoTaskMem allocations with `CoTaskMemFree`.
- Win32 handles are not COM references; cleanup is resource-specific.
- Unknown allocator or ownership contracts fail closed.

## Metadata evidence

Before supporting an interface:

1. Parse the actual configured `Windows.Win32.winmd`.
2. Walk its full interface inheritance chain.
3. Inspect every method, not only the method intended for a sample.
4. Record `NativeArrayInfo`, `FreeWith`, `Const`, parameter direction, and
pointer depth.
5. Record `CanReturnMultipleSuccessValuesAttribute` before deciding whether an
HRESULT is throw-or-void or a semantic result.
6. Resolve every referenced interface IID from the loaded metadata. Require
callers to provide external definitions through `--ref`.
7. Check Microsoft API documentation for ownership that metadata does not
encode.
8. Generate with `--dry-run` and verify unsupported methods stop the whole
unsafe interface projection.

Do not claim general interface support when only a manually described runtime
subset works.

## Fail-closed requirements

Reject generation when any required fact is unknown, including:

- native struct/union layout;
- writable caller-sized buffers without a modeled count relationship;
- untyped output pointers without ownership;
- unsupported interface in/out replacement;
- BSTR arrays or unknown string allocation;
- VARIANT, PROPVARIANT, SAFEARRAY, FORMATETC, or STGMEDIUM without dedicated
models;
- unsupported direct native returns; or
- interface parameters whose IID or PIID cannot be resolved from loaded
metadata;
- parameterized or async interfaces without a computed closed IID;
- delegates without a managed callback projection;
- native arrays without explicit count and element-ownership contracts;
- incomplete inherited vtable layout.

An error during generation is safer than plausible generated code with the
wrong ABI.

## Validation

Every new semantic type or ownership rule needs:

1. a pure unit test for mapping and rendering;
2. a real `Windows.Win32.winmd` regression test;
3. a runtime test covering storage and cleanup;
4. a fail-before/fail-closed test for the nearest unsupported shape;
5. x64 and i686 compile validation for pointer-sized ABI;
6. WinRT regression coverage proving the existing generator and runtime did
not change; and
7. a live stock-Windows E2E when the API is deterministic and requires no
optional software, network, or user interaction.

Prefer tests that add a new ABI shape. Do not add many interfaces that only
repeat activation.

## Review checklist

- Does the change start from metadata facts rather than JS convenience?
- Is the semantic type explicit?
- Are pointer depth and direction preserved?
- Is storage correctly sized before native invocation?
- Is ownership explicit on success and failure?
- Are x86 and x64 widths correct?
- Can a borrowed pointer become a second owner?
- Can Buffer contents be confused with Buffer address?
- Does an InOut path use the same conversion and helper availability as In?
- Does the renderer contain ABI heuristics that belong in projection?
- Does unsupported metadata fail during generation?
- Did any WinRT model, output, or root API change?
Loading
Loading