Classic COM support

September 16, 2026 · View on GitHub

dynwinrt supports a deliberately limited subset of Classic COM. It is not a general Automation or native Win32 projection.

Status: preview, under active development. The current CI baseline against Microsoft.Windows.SDK.Win32Metadata 71.0.14-preview is 5,721 complete safe interface projections out of 7,929 eligible interfaces (72.15%). Earlier inventory and demand-snapshot sections retain the metadata versions and dates stated in those sections.

For installation and application-facing examples, including GUID/IID/CLSID and /com/unsafe, see Classic COM JavaScript usage.

The design keeps the existing WinRT API separate:

import { DynWinRtType, DynWinRtValue } from '@microsoft/dynwinrt';
import { DynComVariant, initializeCom } from '@microsoft/dynwinrt/com';

Both entrypoints use the same native N-API binary and private libffi call machinery. Classic COM metadata, generated wrappers, ownership rules, and public APIs remain separate from the WinRT projection.

The private Node storage, value-sidecar and registration boundaries are described in JavaScript binding internals. Sharing JS backing-store protection does not share COM ownership or ABI semantics.

Language ergonomics belong to codegen projection, after native semantics have been validated. The runtime executes a faithful ABI plan; the JavaScript projection chooses Buffer/string/bigint, naming, hidden ABI parameters, and return shapes; the renderer only serializes those decisions. Classic COM work must not change existing WinRT metadata, generated output, ownership, runtime behavior, or the @microsoft/dynwinrt root API.

Runtime call architecture

WinRT and Classic COM have separate semantic planners. They share only the private native-call backend and executor:

WinRT metadata -> signature.rs (WinRT planner) --------\
                                                        -> native_call.rs -> call.rs -> native method
COM metadata   -> com.rs (COM planner and method table) /
LayerResponsibility
signature.rsWinRT-only signature facade. It preserves the existing In, Out, fill-array, HRESULT, and out-value conventions. It must not expose raw pointers, InOut, native direct returns, or other Classic COM semantics.
com.rsClassic COM types, method signatures, interface roots, vtable slot numbering, method registry, and method handles. It owns raw-pointer, InOut, direct-return, and void call semantics without registering methods in the WinRT MetadataTable.
native_call.rsPrivate lowering backend. It converts a completed WinRT or COM signature into parameter/output slots, validates input values, expands array ABI parameters, chooses a fast path or prepares a libffi CIF, and coordinates result conversion. It does not own metadata, language projection, or public interface registries.
call.rsPrivate native executor. It reads the vtable function pointer, creates stable ABI storage and libffi arguments, performs the call, and decodes raw output storage according to the completed plan. It must not infer WinRT, Classic COM, ownership, or JavaScript semantics.

This separation is semantic, not a duplication of the native executor. WinRT and Classic COM may both lower primitive and struct layout information through the same private backend, but only their respective semantic layers may decide what a type, parameter direction, return convention, or ownership contract means.

In particular:

  • WinRT interface methods remain in the WinRT MetadataTable and begin at IInspectable slot 6.
  • Classic COM maintains its own interface method table and selects slot 3 or 6 from its IUnknown or IInspectable root.
  • shared native methods are fully built before publication and are immutable during concurrent invocation;
  • exact struct identity is validated before native dispatch, while established WinRT ABI aliases such as Char16/U16 and enum/I32 arrays remain compatible; and
  • language-friendly choices remain a codegen responsibility after the COM planner has validated the native contract.

Code generation architecture

Code generation is organized by semantic domain before target language:

codegen/
├── winrt/
│   ├── shared/
│   ├── javascript/
│   └── python/
└── com/
    ├── ir.rs
    ├── model/
    ├── project/
    │   ├── interop.rs
    │   ├── legacy_diagnostics.rs
    │   └── legacy_types.rs
    └── javascript/
        ├── types.rs
        └── render.rs

The Classic COM flow is:

raw Windows.Win32 facts
  -> validated SemanticComInterface / ComMethodContract
  -> COM language projection
  -> validated ComType / ProjectedComMethod
  -> JavaScript and declaration renderer

ComType is a closed set of supported ABI semantics: primitives, transparent scalar typedefs, pointer-sized scalars, BOOL/HRESULT, GUID, HSTRING, enums, explicitly classified handle/data/string pointers, BSTR, raw input pointers, validated native POD values/pointers, explicitly tagged native-union pointers, VARIANT pointer contracts, input-only VARIANT-by-value contracts, SAFEARRAY, PROPVARIANT, and managed interfaces with resolved IIDs. Typed counted buffers carry their validated element ABI and an explicit input-count, caller-capacity/actual-length, or callee-allocation relation. One authoritative element count may also group exactly one borrowed NUL-terminated UTF-16/ANSI string-pointer input array with one caller-owned plain scalar/enum output array. Generated JavaScript accepts string[], hides the count and output storage, and returns a numeric/enum array. Parameter direction, return convention, result ownership, cleanup, buffer relationships, activation, and dynamic-IID behavior are encoded in the projected IR. An optional implementation plan is encoded only for an IUnknown-rooted interface whose complete contiguous vtable maps to the validated callback subset. That subset includes scalar/enum/GUID/handle/interface values, HRESULT/void/direct-scalar returns, BSTR/HSTRING and borrowed string pointers, POD values/pointers, basic InOut, and authoritative plain counted-buffer contracts. Each registered method owns both its outbound ComCallPlan and full inbound CallbackMethodPlan. The runtime chooses a static thunk for a common signature or a cached libffi closure for every other supported signature; the renderer never serializes backend shapes.

Implemented objects may expose multiple independently generated interface views. QueryInterface routes each derived and base IID to its frozen view, every view shares one reference count, and QueryInterface for IUnknown always returns the canonical identity. Generated implementation descriptors compose these views without exposing handwritten signatures.

libffi allocates executable closure memory. A process mitigation such as ProhibitDynamicCode can therefore reject a signature that has no static fast path; object creation reports that failure instead of publishing a partial vtable. Prepared closures are cached for the process lifetime so a callback that performs the final reentrant Release cannot free the machine-code page currently executing. Production projection reads those decisions only from the validated semantic contracts; the shared compatibility metadata supplies names, documentation, and enum member values, not ABI meaning. The legacy TypeMeta projector is retained only to reproduce established unsupported-interface diagnostics and to build synthetic renderer fixtures; successful production generation discards it entirely.

General arrays outside that shared-count subset, parameterized and async interfaces, delegates, unknown layouts, unclassified pointer typedefs, unresolved IIDs, unknown allocators, and unsupported ownership transfers fail during projection. Fixed primitive arrays are accepted only as fields of a completely validated POD layout. The renderer cannot see TypeMeta or metadata attributes and has no default pointer/Buffer fallback; it only serializes the validated projected IR with exhaustive type matches.

Explicit COM overload names

Existing overload groups with distinguishable JavaScript arity/shape retain their exact dispatch and generated API. PR4 handles previously rejected groups only when every member is an otherwise fully validated normal COM method. Groups with colliding JavaScript signatures or projected buffers receive deterministic public names <camelName>AtSlot<absoluteVtableSlot> for every member; the ambiguous unsuffixed method is absent.

The slot is the absolute inherited vtable slot, not an overload ordinal. For example, ID2D1Device1::CreateDeviceContext produces two explicit createDeviceContextAtSlot... members; the generated declarations give their exact names and return types. Native method names, slots, ABI signatures, conversions, and lifetime plans are unchanged. Projection selects the names in IR; the renderer only serializes them and adds no runtime type or ABI guesses.

Alias collisions with actual projected members fail closed. Synthesized, dynamic-IID, and other non-normal method groups still reject; this is not a universal overloaded-method parser or a way around incomplete native contracts. See the usage guide. PR4 does not change COM manifest version 4 or unsafe support schema 12: already-supported safe output remains byte-identical, and the newly admitted groups previously had no valid safe surface.

Bounded native one-shot completion

The first supported flat export is the exact Windows.Win32.Media.Audio.Apis.ActivateAudioInterfaceAsync relationship. It is separate from general interface projection and does not increase the complete-interface census. No general native-export or COM server API is introduced.

The configured Windows.Win32.winmd must contain the following complete contracts. The retained regression uses Win32Metadata 71.0.14-preview, SHA256 B64EE4818A7ED9F9D135038D58C51BD08369184D4D5ED428F20E9DE55DF8121D.

EntryVerified native ABI and contract
MMDevAPI.dll!ActivateAudioInterfaceAsyncSystem ABI, ordinary HRESULT return, exactly five arguments and no this: const NUL-terminated UTF-16 path; REFIID; optional PROPVARIANT* restricted to NULL; borrowed completion-handler interface; required cell receiving an owned operation +1.
IActivateAudioInterfaceCompletionHandlerIID 41d949ab-9862-444a-80f6-c261334da5eb, direct IUnknown inheritance, only slot 3 ActivateCompleted, ordinary HRESULT, one borrowed operation-interface input.
IActivateAudioInterfaceAsyncOperationIID 72a22d78-cde4-431d-b8cc-843a71199b6d, direct IUnknown inheritance, only slot 3 GetActivateResult, ordinary outer HRESULT, required HRESULT* and IUnknown** output cells.

The export parser preserves named types, pointer depth, direction, optional and Const attributes, import library, entry point, and calling convention. The COM projector validates the whole relationship before emitting a versioned, closed runtime descriptor, plus the complete safe IAudioClient and IAudioEndpointVolume projections. Unknown signatures and contract drift fail during dry-run/generation and again at runtime descriptor validation. No renderer infers ownership from method names.

Metadata does not mark the contained activatedInterface pointer nullable. The exact GetActivateResult contract documents NULL on inner activation failure; its receiving cell is still required. The native collector interprets start HRESULT, outer result HRESULT, inner activation HRESULT, and NULL independently, with RAII cleanup of written owned outputs even on failure. Successful result QI and the public projectAs registry run on the owner thread. The owned result is not placed in a JavaScript native-value carrier until that carrier has a registered finalizer. A failing N-API class construction during worker shutdown therefore still releases the result on its owner.

com_completion.rs prepares bounded libffi export/result plans using the existing system-ABI CIF helper. The allowlisted DLL is resolved from System32 and pinned for late native completion after Node teardown. It does not call a compiled per-interface SDK adapter, invent a COM receiver, or permit production function-address injection. The signal's own code module is also pinned before publication, so a callback cannot jump into an unloaded addon after the last Node environment using it has closed.

The private signal sink reuses DynamicComSink's HRESULT/interface-input static thunk. It has canonical IUnknown identity, the exact callback IID, IAgileObject, and a real IMarshal delegated to an aggregated CoCreateFreeThreadedMarshaler. This is an internal implementation detail, not general aggregation support. The free-threaded state contains only a pointer-free publication/completion rendezvous and a native token notifier, never an operation, result, or JS callback. A temporary in-flight reference protects the sink; callback input QI/borrowed references are released in that callback's apartment.

Node keeps the original start-returned operation, handler, deferred, and plan in an owner-thread-local record that is not Send/Sync. The native signal only enqueues a process-unique token through ManagedTsfn's native-dispatch mode. Publication gates early callbacks, duplicate callbacks notify once, and no operation→handler→operation cycle is formed. Owner dispatch collects results only after the signal; it neither casts to WinRT IAsyncInfo nor assumes that the operation or audio result is agile. Ordinary JS callback owner-thread guards and WinRT async dispatch are unchanged.

One-shot notifier disposal releases the TSFN even if Windows retains the handler. There are at most 256 pending records per environment, including reservations made before a potentially reentrant start call. Cleanup closes every signal before releasing owner COM references; late/stale tokens cannot address a new record in a recycled environment. Dropping the JS Promise does not cancel native work. No timeout, AbortSignal, loopback parameters, or arbitrary cross-apartment JS scheduling is exposed.

Deterministic tests use an ABI-correct five-argument fake export, complete operation and audio-result vtables, actual FTM QI, and initialized MTA delivery through RoGetAgileReference/Resolve. Fake non-agile results are created only in owner-thread GetActivateResult. Coverage includes early/duplicate completion, all HRESULT/NULL stages, exactly-once lifetime, stale/recycled tokens, callback-in-flight teardown, addon lifetime after the last environment closes, pending-work bounds, and event-loop exit. This deterministic suite does not activate hardware or record audio. The separate opt-in DYNWINRT_TEST_AUDIO_RENDER=1 smoke only reads the default speaker endpoint's channel count through the real export. Caller/UI-thread requirements remain those of ActivateAudioInterfaceAsync.

Bounded borrowed-buffer copies

Audio, WIC, and linear Media Foundation storage share a COM-local BorrowedCopyPlan. These are synchronous owned-copy transactions, not native-backed Node Buffer/ArrayBuffer views or caller-asserted pointer leases. They preserve the metadata → semantic contract → completed MethodHandle/libffi call → projection boundary. The runtime contains no per-interface SDK calling adapter; typed SDK acquisition is confined to stock-Windows tests.

Generated receiverOwned-copy operationsExtent and finalization
IAudioRenderClientwriteFramesCopy(data), writeSilence(frames)Frames use the format from observed successful initialization and must fit native GetBufferSize. Release the complete request, or zero frames on abort.
IAudioCaptureClientreadPacketCopy()Native packet frames × proven initialized block alignment, bounded by native capacity. Return an owned data packet, a tagged silent packet, or null for empty. Release the full packet after copying, zero on abort.
IWICBitmapreadLockedBgra8Copy(rect)Acquire a READ lock, obtain native size/stride/pixel format/byte count, copy row pixels without padding, release the owned lock reference.
IMFMediaBufferreadCopy(), replaceCopy(data)Request both native current and maximum lengths. Copy current bytes, or replace within maximum and call SetCurrentLength; always Unlock after successful Lock.

Render/capture and MF are explicitly copy-only facades, not complete native interface projections. They do not expose GetBuffer, ReleaseBuffer, Lock, or Unlock independently. WIC retains its previously complete native interface surface (including acquisition of an opaque owned lock) and adds the copy operation. An external lock never gains byte access or writable rights from this operation. The three copy-only facades remain excluded from the current 5,721 / 7,929 complete safe census.

Private context and lifecycle

The native-independent dynwinrt-com-contracts registry is the single reviewed source for complete interface identities, native IID/slot/ABI-cell evidence, operation/result mappings, and lifecycle recipes. Codegen selects records by exact identity; the runtime admits only an exact packaged record. A matching-looking JSON object or a new record ID does not authorize native memory access.

The version-2 recipe actually drives execution:

  • named calls reference reviewed evidence and bind each native argument to a typed input source or named output cell;
  • before, acquire, and after order queries and acquisition; typed byte, frame, layout, rectangle, row, flag, timestamp, and owner references must have available producers, compatible ABI cells and matching native owners;
  • the closed frame-extent, native-extent, and row-extent constructors prove the copy bounds, while transfer chooses read, write, silent-packet skipping, or no-payload silence;
  • commit consumes the proven replacement length, and cleanup supplies the actual normal/abort arguments or names the unique acquired owner to Release;
  • result names the bounded dimensions, frames, flags and timestamps to project.

The pure validator checks the entire recipe, including abort availability before acquisition, full/zero frame release, access, result shape and owner lifetime, before preparing any native call. Reviewed evidence designates each acquisition's finalizer and the units of each native scalar cell; a same-width flags/count swap or unrelated cleanup call cannot satisfy the graph proof. Runtime native lengths still require checked arithmetic and pointer/extent validation after successful acquisition. All calls use completed MethodHandles/libffi, with no family execution switch or per-interface SDK adapter. The public methods are thin operation selectors. This is not an arbitrary-execution DSL: no pointer input, caller byte extent, user callback, loop, allocator selection or new production interface is introduced.

Output cells are private stable storage, not language values: the executor cannot publish/read them before classifying the exact HRESULT. Cleanup is armed before inspecting acquired outputs or allocating/copying result bytes.

Idle -> Acquiring -> Active -> Finalizing -> Idle
                  \ unexpected success / cleanup failure -> Poisoned

The canonical-IUnknown sidecar is private to COM carriers. Owner-thread registry entries are weak; each live sidecar strongly retains its canonical identity, preventing stale address-key reuse. A second process-local weak map contains only pointer-free owner-thread tokens and rejects independent cross-thread claims for the same canonical identity. No native reference or Rc state crosses that map, and no registry lock spans native dispatch. Managed COM carriers also share an initially empty context slot from creation, pinned by their owned references. Thus aliases created before the first copy still retain its later provenance or poison after the first copier is released, without adding a canonical-reference pin to unrelated ordinary COM calls. A service retains its originating client context and exact client interface view, without a reverse sidecar reference or cycle. QI, projectAs, and independently rewrapped aliases recover the same context while any managed holder retains it. Conflicting mappings fail closed.

Ordinary COM invocation also admits every managed interface argument, not just the receiver. The shared input boundary checks apartment ownership and the current busy/poisoned state before conversion and native dispatch. Supported interface-bearing containers retain the same canonical-identity context slots as their elements; they do not gain a fresh lifecycle by cloning native references. A container created while idle must still reject a subsequently poisoned element, including after the original wrappers have been released. This is a common argument rule, not an IMFSample::AddBuffer special case. Standard identity handling and deterministic release remain available for cleanup; ordinary invocation does not make every unrelated idle object busy. The final admission check runs after native argument coercion, including QI, so reentrancy cannot invalidate an earlier check unnoticed. Rejection keeps the dispatch marker clear and releases call-local storage. The private lowering hook is generic; COM state policy stays in the COM binding layer.

The receiver and required owner gates enter Acquiring before native dispatch. No registry/state lock is held across a native call. A successful acquire arms cleanup before pointer/range validation or allocation. Direct JS callback entry is rejected while the synchronous transaction is in progress; no JS callback receives a borrow. Finalization is taken exactly once before it runs. Failure poisons the shared context, suppresses all output bytes, and never triggers a speculative Drop retry. Writes are not promised atomic rollback, particularly if SetCurrentLength fails after copying.

Operations reject off the owning thread before touching native references. Wrong-thread COM carrier destruction retains the existing leak-rather-than- wrong-apartment-release policy, including the private sidecar.

Audio initialization is the extent proof

Only actual successful calls carrying the exact metadata effect can record provenance:

  • IAudioClient::Initialize, slot 3, format argument 4, share-mode argument 0, stream-flags argument 1;
  • IAudioClient3::InitializeSharedAudioStream, slot 20, format argument 2, stream-flags argument 0, implicit shared mode;
  • IAudioClient::GetService, slot 14, binds the returned render/capture service to that client only after actual native success and verified service QI.

These effects are inherited by the generated IAudioClient2/IAudioClient3 views. Failed or AUDCLNT_E_ALREADY_INITIALIZED calls neither create nor overwrite the immutable record. There is no attach-format, assume-initialized, or record-success API. An externally initialized client without observed provenance cannot use the copy operations. GetMixFormat and a caller's blockAlign are not proof: AUTOCONVERTPCM can make the initialized format different from the engine mix format even in shared mode.

Copy capability is granted only for validated PCM 8/16/24/32-bit containers, IEEE float 32/64-bit containers, and supported 22-byte WAVEFORMATEXTENSIBLE equivalents. Channels, valid/container bits, optional channel mask, block alignment, sample rate and average byte rate must agree. Other valid WAVEFORMATEX values retain initialization support but do not grant byte-copy capability. Checked arithmetic and a 64 MiB operation cap restrict, but never establish, a native extent.

For render, IID f294acfc-3146-4483-a7bf-addca7c260e2, the plan uses GetBuffer slot 3 and ReleaseBuffer slot 4. Input is staged into owned Rust bytes before acquisition; frames are derived from that byte count. Zero requests do not acquire/release a buffer. Silence uses AUDCLNT_BUFFERFLAGS_SILENT without touching the payload pointer. Exclusive event-driven render is excluded because a zero-frame abort is not valid for that mode.

For capture, IID c8adbd64-e71e-48a0-a4de-185c395cd317, GetBuffer slot 3 has five output cells, and ReleaseBuffer is slot 4. The exact semantic-HRESULT override recognizes only S_OK as acquisition and AUDCLNT_S_BUFFER_EMPTY as no acquisition. Empty returns without reading any output cell or releasing a packet. Unknown success poisons rather than guessing. SILENT ignores the data pointer, including NULL; TIMESTAMP_ERROR makes timestamps unavailable. The plan never calls shared-only GetNextPacketSize, so it also handles exclusive capture. Automated capture coverage is fake-only, never hardware recording.

WIC and MF limits

IWICBitmap IID 00000121-a8f2-4877-ba0a-fd2b6645fb94 uses absolute slot 8 Lock with an explicit validated rectangle and fixed READ flags=1. Its owned IWICBitmapLock IID 00000123-a8f2-4877-ba0a-fd2b6645fb94 supplies GetSize/Stride/DataPointer/PixelFormat at slots 3/4/5/6. The current apartment must be STA because GetDataPointer is unavailable in MTA. Only BGRA8 GUID 6fddc324-4e03-4bfe-b185-3d77768dc90f is accepted. The last-row span (height - 1) * stride + width * 4 must fit the native byte count; final-row padding is not required or read. The returned Buffer contains packed rows. There is no invented Unlock: cleanup releases the acquired lock.

IMFMediaBuffer IID 045fa593-8799-42b8-bc8d-8968c6453507 uses Lock/Unlock at slots 3/4 and SetCurrentLength at slot 6. Both Lock length outputs are requested, current <= max is checked, and the returned pointer is never freed with CoTaskMemFree. Lock is not native cross-thread synchronization: the gate coordinates this library's aliases, not arbitrary external users. General signed-pitch/plane IMF2DBuffer/IMF2DBuffer2 views remain unsupported. Caller-provided height or stride is never treated as native memory bounds.

Evidence and regeneration

The shared packaged registry contains the same 26 exact selectors, full source fingerprints and Microsoft citations. com_borrowed_metadata.rs checks them against the configured metadata, which must include Win32Metadata 71.0.14-preview, SHA256 B64EE4818A7ED9F9D135038D58C51BD08369184D4D5ED428F20E9DE55DF8121D. The complete inherited interfaces and required owner/lock dependencies are validated, not just acquisition methods. Runtime descriptors are versioned and closed; absent/drifted signatures, storage roles, prerequisites or finalizers fail before acquisition. Renderers only serialize the projected IR. Borrowed descriptors are now version 2, and the generated COM ownership manifest is version 5. Old descriptors/manifests require complete regeneration; there is no silent or incremental migration. Evidence IDs, fingerprints, support sets and public declarations are unchanged; embedded descriptor JavaScript and the ownership-manifest version necessarily differ.

Hardware-free tests add test-only records for new linear, frame-writer, frame-reader and row-copy interfaces (plus their owner/context views). Every native copy call uses a different IID and slot, reordered argument/output bindings, and renamed/reordered calls. Real copies and full/zero or owner cleanup execute without changing production model/executor dispatch. The fixtures also exercise ABI-sized storage on x64/i686, short last rows, silent/empty packets, commit failure and exactly-once poisoning. Test registry injection is absent from production and JS test-hooks builds.

COM file-ownership manifest 5 and unsafe support schema 12 require deletion and full regeneration of older output. Do not mix pre-effect audio wrappers with new copy facades. Coverage includes native fake tear-offs with complete SDK-correct vtables, truthful QI, alias/context lifetime, failed initialization and differing mix formats, no-JS reentrancy, exact-once poisoned cleanup, all packet states, WIC final-row/stride cases, MF failure paths, and live stock-Windows WIC/MF. Both x64 and live i686 execute the same runtime tests.

Retained PR3 validation before the version-2 execution-plan refactor:

CheckResult
Core + codegen suitesAll pass, including WinRT snapshots and 306 core / 421 codegen / 66 CLI unit tests; the existing WinAppSDK-dependent initialization test remains ignored.
Borrowed-copy native ABI20 tests pass on x64 and 20 on live i686; i686 N-API binding compile passes.
Node functional regressions89 pass, including eight copy scenarios, ten one-shot scenarios, existing generated ownership contracts, package/type checks and callback regressions.
Stock-Windows E2E38 WinRT and 17 Classic COM scenarios pass. WIC and linear MF copy transactions also pass against real OS objects in the native suite.
Census determinismTwo independent runs produce seven byte-identical artifacts; the complete count remains 5,697.

The separate WinUI scheduled-start test has a timing-sensitive 1,000 ms nextTick assertion. AVA runs intermittently exceeded it on this host. An exact build of parent commit 247383c reproduced the same failure (1,729.8 ms); the current build also passed in isolation. Three paired direct runs of the unchanged harness passed for both parent and current runtimes (roughly 132–150 ms). No timing threshold, WinUI implementation, or preceding-PR invariant was relaxed.

Size of Windows.Win32.winmd

The counts below are exact for Microsoft.Windows.SDK.Win32Metadata 69.0.7-preview Windows.Win32.winmd, read with windows-metadata 0.59.0. <Module> is excluded.

There is no single canonical definition of an "API" in ECMA-335 metadata. For callable entries, the most useful count is:

17,760 flat P/Invoke functions
+46,233 declared interface methods
=63,993 callable entries
Metadata entityCount
Namespaces324
Type definitions35,055
Flat P/Invoke functions17,760
Interfaces7,971
IUnknown-rooted interfaces7,878
IInspectable-rooted interfaces43
Other/no-root interfaces50
Declared interface methods46,233
Structs15,944
Enums7,784
Enum members67,587
Delegates3,002
Classes/API containers316
Metadata attributes38
Non-enum literal constants88,931

These numbers describe the metadata, not dynwinrt support:

  • Classic COM primarily targets interface methods. Apart from the bounded native-completion export above, it does not project the 17,760 flat DLL exports.
  • An interface declaration may describe a caller-implemented callback rather than an OS object that can be activated and called.
  • The interface count includes graphics, media, WMI, Automation, Shell, and other families whose native types are not all supported.
  • Methods inherited by a derived interface are counted once where they are declared, not repeated for every derived interface.

The largest flat-function modules in this metadata version include KERNEL32.dll (1,407), USER32.dll (767), gdiplus.dll (629), ADVAPI32.dll (619), GDI32.dll (431), OLEAUT32.dll (405), OLE32.dll (273), and SHELL32.dll (244).

Type-system problem map

The following counts come from all 46,233 declared interface methods, not only the 30-interface frequency sample. Nested pointee types are included in type occurrence counts.

Signature characteristicCount
Parameters79,181
Input parameters47,289
Output parameters28,058
In/out parameters3,834
Optional parameters5,362
HRESULT returns44,309
Direct void returns1,018
Direct value returns906
Mutable pointer occurrences, depth 136,321
Mutable pointer occurrences, depth 21,492
Mutable pointer occurrences, depth 38
Parameters with NativeArrayInfo2,973
Parameters with FreeWith metadata13
Unique referenced interfaces2,875
Unique referenced structs1,491
Unique referenced enums1,739
Unique referenced delegates71
BSTR occurrences7,697
VARIANT-family occurrences3,586
SAFEARRAY occurrences238
PROPVARIANT occurrences156
PROPERTYKEY occurrences138
Representative audio-format struct occurrences69
FORMATETC/STGMEDIUM occurrences27

The implementation should therefore be planned around the following problems, not around one-off interface fixes.

1. Native layout engine

Problem: A named native type is not enough to call a method. The ABI needs its exact size, alignment, packing, field offsets, nested layout, architecture variation, and whether it is a struct or union.

This is the largest general blocker: 1,491 distinct structs appear in interface signatures. It affects Direct3D, DXGI, Shell, drag-and-drop, streams, WMI, audio, and the Property System.

Required model:

  • sequential and explicit layout;
  • nested structs and unions;
  • fixed arrays and bitfields;
  • x86/x64/ARM64 size and alignment;
  • by-value, pointer-to, out, and in/out forms; and
  • safe construction and field access in each language binding.

2. Pointer depth and pointee semantics

Problem: T*, T**, and T*** are not interchangeable. A pointer may mean a borrowed object, optional value, caller storage, callee allocation, array, null-terminated string, interface reference, or opaque token.

The metadata contains 37,821 pointer occurrences, including 1,500 with depth greater than one.

Required model:

  • pointee type and pointer depth;
  • const versus writable storage;
  • nullable versus required;
  • interface pointer versus data pointer;
  • input, output, and replacement/in-out semantics; and
  • storage size before a native call is allowed.

3. Counted buffers and native arrays

Problem: A pointer plus count is one logical value. Allocating one scalar for a writable BYTE* is a memory overwrite.

There are 2,973 NativeArrayInfo parameters. The projection needs:

  • which parameter supplies the count;
  • whether the count is bytes or elements;
  • capacity versus actual returned length;
  • caller-allocated, callee-allocated, and two-call sizing patterns;
  • string termination and encoding; and
  • partial writes and failure cleanup.

The supported subset includes primitive typed input buffers, caller-owned outputs with exact capacity/actual-length relationships, byte-counted ISequentialStream::Read/Write, exact bounded two-call sizing contracts, and known-allocator callee outputs. A distinct exact IEnum*::Next plan supports IEnumGUID and IEnumConnectionPoints: requested capacity and fetched count remain separate, S_FALSE is successful partial completion, and generated next(count) returns only the fetched GUIDs or managed interface wrappers. Counts are never inferred from names or adjacency. Other owning BSTR/interface/string-pointer elements and unknown allocators remain fail closed.

4. Ownership and allocator contracts

Problem: The type and pointer depth do not identify who owns memory or how to release it.

Only 13 parameters in this metadata carry FreeWith, despite thousands of owned-output contracts. Metadata alone is therefore insufficient.

The ABI/projection needs explicit ownership such as:

  • borrowed;
  • COM AddRef/Release;
  • BSTR / SysFreeString;
  • CoTaskMemFree;
  • LocalFree;
  • allocator/interface-specific release;
  • Win32 resource-specific cleanup; and
  • custom or unknown ownership, which must fail closed.

5. Discriminated unions: Automation and Property System

Problem: VARIANT and PROPVARIANT combine a type tag, a union payload, and nested ownership. Treating either as an opaque pointer is not a complete or safe projection.

The implemented Automation subset now provides:

  • VARIANT empty/null, signed/unsigned integers, float/double, exact VARIANT_BOOL, BSTR, IUnknown, IDispatch, and supported SAFEARRAY values;
  • PROPVARIANT scalar numeric/bool values, LPWSTR, CLSID, FILETIME, blob, and supported vectors;
  • VariantInit/VariantClear and zero-init/PropVariantClear;
  • distinct VARIANT pointer and input-only by-value ABI categories. By-value calls use a call-local VariantCopy, pass the real aggregate through libffi, and clear the copy on every exit path; required aggregates never fabricate null or VT_EMPTY;
  • dedicated DISPPARAMS input storage that deep-copies and reverses natural arguments into stable contiguous VARIANTARG storage;
  • dedicated EXCEPINFO output storage with exact BSTR cleanup and one-shot deferred-fill handling;
  • dedicated COM-only JavaScript wrappers and range-checked conversions; and
  • fail-closed BYREF/InOut, unknown VARTYPE, and unsupported nested ownership.

Complete inherited IDispatch now projects from real metadata. GetIDsOfNames retains its natural shared-count array surface, and Invoke takes DynComDispatchParams plus explicit LCID, flags, IID, and optional-output request options. XML Automation and Task Scheduler still stop at their own unsupported BYREF/InOut or nested-ownership contracts; support for IDispatch inheritance does not imply those derived interfaces are complete.

6. SAFEARRAY

Problem: SAFEARRAY is a descriptor, not a pointer to a flat JavaScript array. It carries rank, bounds, element type, locks, ownership, and potentially non-blittable elements.

The runtime supports ranks 1 through 8, signed/non-zero lower bounds, typed scalar/VARIANT_BOOL/BSTR/IUnknown/IDispatch/VARIANT elements, overflow and length validation, and exact element cleanup. It uses SafeArrayCreate, SafeArrayCopy, SafeArrayGetVartype/dimension/bounds/element-size APIs, and SafeArrayAccessData/UnaccessData; descriptor bytes are never reinterpreted.

7. Interface in/out and callback implementations

Problem: Replacing IFoo* through IFoo** requires precise release and AddRef behavior. Event APIs additionally require dynwinrt to implement an arbitrary caller-defined COM interface, not merely invoke one.

Required support:

  • release of the old in/out reference when the contract requires it;
  • ownership of the replacement reference;
  • generated sink vtables;
  • QueryInterface identity and reference counting for implemented objects;
  • callback threading/apartment dispatch; and
  • conversion of callback failures to HRESULT.

The dynamic implementation backend now provides generated vtables, canonical multi-interface identity, static fast paths plus libffi closures, owner-thread dispatch, and fail-closed output validation. Interface InOut replacement remains unsupported because its old/new reference ownership is not encoded strongly enough.

8. Semantic HRESULT values

Problem: Most HRESULTs are throw-or-success, but methods such as IPersistFile::IsDirty use S_OK versus S_FALSE as their actual result. Discarding every successful HRESULT loses information.

Windows.Win32 metadata marks these methods with CanReturnMultipleSuccessValuesAttribute. The COM projection preserves the numeric successful HRESULT for marked methods while still throwing failed HRESULTs. Exact documented exceptions such as IPersistFile::GetCurFile, whose metadata omits the marker, are classified explicitly. Other unmarked HRESULT methods retain the normal throw-or-void behavior.

9. Apartment affinity and marshaling

Problem: A valid COM reference is not necessarily callable from every thread. STA objects require the owning apartment or a marshaled proxy.

Generated Classic COM JavaScript wrappers now record the creating thread and reject wrong-thread invocation or explicit release. A wrong-thread finalizer leaks rather than calling Release in the wrong apartment. WinRT values remain unbound and retain their existing behavior.

Remaining apartment work includes:

  • agile-object detection;
  • Global Interface Table or COM marshaling integration; and
  • deterministic callback dispatch to the correct apartment.

10. Acquisition and flat-function boundary

Problem: many common interfaces are not created with CoCreateInstance. Examples include CoGetMalloc, CreateBindCtx, D2D1CreateFactory, DWriteCreateFactory, D3D11CreateDevice, and shell helper functions.

The Classic COM layer invokes acquired interfaces; DLL exports use the separate contract-driven flat Win32 layer and @microsoft/dynwinrt/win32 entrypoint. Its supported calling conventions, status/LastError behavior, buffers, and resource cleanup are validated independently. This does not imply support for every interface-acquisition function or native callback shape.

  1. Extend validated POD layout to unions, bitfields, and owned/non-POD fields.
  2. Pointer-depth plus counted-buffer contracts.
  3. Explicit allocator/ownership metadata.
  4. VARIANT/PROPVARIANT and semantic HRESULT handling.
  5. SAFEARRAY.
  6. Broaden generated COM sink/interface implementation beyond the initial same-thread interface-input subset.
  7. Apartment-aware marshaling.
  8. Keep DLL-export acquisition/invocation separate from COM vtable semantics.

Current Classic COM implementation

The Classic COM implementation provides a validated subset and rejects unsupported contracts. It does not solve every problem in the map above.

Implemented

ProblemCurrent implementation
WinRT/Classic COM separationSeparate COM metadata/codegen path and @microsoft/dynwinrt/com public entrypoint. The WinRT generator and root runtime API remain unchanged.
Interface root and vtable layoutDistinguishes IUnknown slot 3 from IInspectable slot 6 and walks inherited Classic COM interfaces before assigning slots.
Method return conventionsSupports normal HRESULT methods, semantic HRESULT values marked with CanReturnMultipleSuccessValuesAttribute, native direct scalar, direct pointer at the runtime layer, and direct void returns.
Basic parameter directionSupports input, output, scalar in/out, and documented Automation BSTR replacement parameters without reducing in/out to out-only.
Primitive ABI typesSigned/unsigned integers, floats, BOOL, HRESULT, GUID, enums, and char16.
Pointer-sized valuesISize/USize select the correct x86/x64 ABI width and JavaScript uses bigint.
Validated native POD layoutArchitecture-specific sequential layouts and authoritative non-overlapping explicit layouts support primitive, enum, GUID, pointer-sized, nested POD, and fixed primitive-array fields. Layout computation checks packing, alignment, bounds, overflow, overlap, and recursive cycles for x86, x64, and ARM64. JavaScript uses branded native-struct objects exposing a copied .bytes Buffer; qualified identity and exact size are checked before calls.
Native unionsArchitecture-specific overlapping fields are validated at offset zero. Only scalar/GUID/pointer/nested POD struct fields are accepted. JavaScript uses branded DynComNativeUnion values whose constructor requires an explicit active field. Union pointer inputs are supported; by-value unions, outputs without a discriminant contract, nested unions, bitfields, flexible arrays, and nested owned fields fail closed.
VARIANTDedicated DynComVariant values support VT_EMPTY, VT_NULL, I1/UI1/I2/UI2/I4/UI4/I8/UI8/INT/UINT, R4/R8, exact VARIANT_BOOL, BSTR, UNKNOWN, DISPATCH, and supported VT_ARRAY values. Pointer-shaped input/output contracts retain their existing path. Required input-only VARIANT/VARIANTARG values may also lower by value: windows-rs establishes size/alignment 24/8 on x64 and 16/8 on i686, libffi receives an aggregate rather than a pointer, and a call-local VariantCopy protects shared storage and balances nested BSTR/interface ownership. Optional by-value metadata, output/InOut, BYREF, and unknown flags/tags fail before dispatch.
SAFEARRAYDedicated DynComSafeArray values support ranks 1–8, explicit signed lower bounds, typed scalar/bool/BSTR/interface/VARIANT elements, exact length/overflow/VARTYPE/element-width validation, lock/unlock, copy, and destruction. JavaScript preserves bounds rather than silently forcing zero-based arrays.
PROPVARIANTDedicated DynComPropVariant values support empty/null, scalar integer/float/bool, LPWSTR, CLSID, FILETIME, BLOB, and vectors of numeric/bool/string/GUID/FILETIME values. Storage is zero-initialized and cleared exactly once with PropVariantClear; nested VT_VECTOR|VT_VARIANT and unknown combinations fail closed.
DISPPARAMSDedicated DynComDispatchParams owns deep-copied VARIANTARG values, reverses natural argument order for IDispatch, preserves named DISPIDs, and keeps descriptor/array pointers stable through the call.
EXCEPINFODedicated output storage is zero-initialized, invokes pfnDeferredFillIn at most once, validates reserved fields, extracts source/description/help-file/context/scode, and frees every BSTR on all success and failure paths.
GUID ABIFull 16-byte GUID output storage plus GUID value and REFIID/REFGUID pointer patterns.
Unsigned enum valuesCOM-local enum metadata preserves unsigned values, including 32-bit high-bit flags and 64-bit bigint literals.
Standard COM referencesCoCreateInstance, QueryInterface, and typed interface outputs carry an owned +1 reference and release automatically.
Ownership provenanceBorrowed numeric/TypedArray pointers cannot be re-adopted as a second COM owner. Native owned outputs are consumed once.
Backing-storage lifetimeBuffer/TypedArray owners are retained and detached ArrayBuffers are rejected before native use.
Common string ownershipBy-value BSTR input is a caller-owned call-local allocation borrowed by the callee; scalar BSTR output and final BSTR replacement results are caller-owned and use SysFreeString. Supported PWSTR/PSTR allocations use CoTaskMemFree.
HSTRING ownershipClassic COM methods that explicitly use HSTRING project strings through owning HSTRING values; outputs release with WindowsDeleteString.
External interface metadataInterface parameters require a resolvable IID. Missing referenced metadata fails generation with a --ref diagnostic instead of degrading an owned interface to a raw pointer.
WinRT runtime-class referencesA resolved runtime class lowers through its default interface IID and remains a managed COM value. Missing defaults fail closed.
Common interop patternSupports HWND + REFIID + void** bridges and adopts the returned interface reference.
Explicit COM initializationActivation no longer silently chooses MTA; callers select STA or MTA with initializeCom(). Generated implementation files use the isolated unsafe runtime internally.
Dynamic JavaScript COM implementationsAny IUnknown-rooted interface whose complete contiguous vtable maps to the validated callback subset receives static implement() and static implementation(). Static fast-path thunks cover common signatures; cached libffi closures cover arbitrary supported parameter counts, scalar widths, POD layouts, outputs, and native return conventions using the platform COM calling convention. Objects support derived/base IID aliases, multiple interface views, canonical IUnknown identity, shared atomic AddRef/Release, synchronous owner-thread JS dispatch, required Out initialization/validation, allocator-correct transfer, and panic/exception containment. Count/capacity values are read according to their In/InOut ABI direction, fixed outputs without an actual-length slot require exact size, and typed interface outputs are queried to the declared IID. QI references, BSTR/HSTRING values, and CoTaskMem buffers remain RAII-owned until every output is prepared; only then are all native output slots committed, so preparation failure leaves owned outputs null and releases every temporary owner. Wrong-thread HRESULT methods return RPC_E_WRONG_THREAD; direct returns are zeroed and void methods do nothing because those native ABIs have no error channel. IFileDialogEvents is live-tested with Advise/Unadvise; IDropTarget exercises libffi, POD/InOut, generated multi-interface composition, and QueryInterface.
Fail-closed generationUnknown/unsafe layouts, untagged/by-value/output unions, bitfields, flexible arrays, nested owned fields, unsupported VARTYPE/BYREF/SAFEARRAY/PROPVARIANT combinations, unsupported arrays, pointer outputs, ownership, and in/out shapes stop generation with a targeted error.
Consumable outputClassic COM files live under com/, with ./com and ./com/* package exports. The generated package root is always WinRT-only; COM-only output deliberately has no root entrypoint.
Explicit vtable registrationEvery generated method is registered with .addMethodAt(vtableIndex, name, signature), keyed by its actual metadata-derived vtable slot. Methods are never deduplicated by name, so same-name overloads at different slots both register correctly.
Same-name overload projectionExisting distinguishable overloads (e.g. IDCompositionEffectGroup::SetOpacity) retain their single public dispatcher, validated arity/shape keys, and contiguous TypeScript overload signatures. Previously rejected groups of fully validated normal methods use explicit <camelName>AtSlot<absoluteVtableSlot> names for every member and omit the ambiguous unsuffixed name. Projection selects these names; the renderer does not infer ABI semantics. Collisions and non-normal groups fail closed.
Lifecycle ergonomicsGenerated interface wrappers declare a protected constructor (protected constructor(obj: unknown);) so only generated coclasses can subclass them. Coclasses expose a public zero-argument constructor, and every wrapper provides an idempotent release() that delegates to the managed native value. Factory-activated interop wrappers retain static create() with JSDoc reminding callers to initialize COM first.
Doc-link renderingWhen win32metadata attaches a DocumentationAttribute (a learn.microsoft.com URL) to a method, the generator renders it as an @see {@link ...} comment in both .js and .d.ts. No raw metadata is imported into the renderer — the URL is threaded through ProjectedComMethod.doc, populated once during projection.
Acronym-aware parameter casingParameter names are lowered using the same acronym-run-aware rule as method names, so a Hungarian-prefixed trailing acronym like hwndMDI projects as mdi (not the previous naive mDI).
Wide/ANSI string pointer splitPointerAliasKind::StringPointer now carries a StringEncoding (Wide/Ansi). Generated wrappers call the semantically distinct DynCom.wideStringPointer(value) / DynCom.ansiStringPointer(value) constructors instead of one unqualified pointer helper; the renderer never infers encoding — it only renders the encoding decision already made during projection.
Output ownership provenanceSignature expressions for pointer-shaped [out] values are chosen from ProjectedComResult ownership/conversion facts, not from type names: DynCom.ownedComPointerType() for dynamic-IID +1 COM outputs, DynCom.coTaskMemPointerType() for CoTaskMem PWSTR/PSTR outputs, dedicated DynCom.bstrType() for BSTR outputs/replacements, and plain pointerType() (never consumable as an owned value) for everything unclassified/borrowed.
Invocation validationThe completed native-call plan validates the exact argument count and ABI-compatible value shape before selecting a direct or libffi path. Native pointers reject scalar/object values, mismatched widths fail before dispatch, and established WinRT JS aliases (I32 projected as i8/u8/char16) are range-checked and converted to exact ABI storage.
Failure-path cleanupEach output parameter carries an explicit cleanup plan. If a callee writes an owned value and then returns a failing HRESULT, interface references are released, HSTRING/BSTR values are deleted with their matching APIs, and CoTaskMem outputs are freed on both direct and libffi paths. BSTR InOut uses one boxed call-local slot, so unchanged, replaced, and nulled values remain safely cleanable on success and failure without exposing shared storage. Unconsumed successful outputs retain the same allocator-specific ownership until converted, adopted, released, or garbage-collected.
Typed counted buffersCOM-local plans support [in] T* + count, caller-owned T* + capacity, separate or in/out actual lengths, bounded exact two-call sizing, and known-allocator T** + count output. Generated JavaScript derives hidden ABI counts, accepts Buffer/TypedArray storage, and returns only initialized bytes.
Exact fixed-capacity byte outputIMFAttributes::GetBlob is registered by declaring IID, qualified interface, slot, and complete parameter/return signature. Generated getBlob(guidKey, capacity) allocates exclusive zeroed runtime storage, hides pBuf/cbBufSize/pcbBlobSize, bounds the allocation, and returns only the successful actual byte range.
Enumerator partial arraysExact IID/name/slot/type registries validate standard IEnum*::Next(ULONG, T*, ULONG*). Runtime storage is exclusive, aligned, stable, bounded, and zeroed. fetched > capacity is rejected without an out-of-bounds read. Interface slots transfer each fetched +1 reference once; failed HRESULTs, conversion errors, overflow, and unused initialized capacity release every remaining non-null slot. Exact canonical IUnknown elements use DynWinRtValue[] directly without generating an IUnknown.js wrapper. Generated next(count: number): T[] rejects zero, fractions, and values above u32::MAX; pceltFetched is hidden and omitted only where exact metadata permits it for count == 1.
Exact borrowed HWND outputsTwenty-two Microsoft-documented declarations are registered by namespace, interface IID, method, slot, parameter count/index/name, and full native shape. Runtime storage is zeroed and pointer-sized, successful null is 0n, and neither success nor failure calls Release, DestroyWindow, or another cleanup routine.
Shared caller-sized arraysOne authoritative input count may group a borrowed NUL-terminated string-pointer input array with one caller-owned plain scalar/enum output array. Runtime-owned encoded strings and pointer tables remain stable through dispatch; aligned output storage is zeroed, count agreement is checked before dispatch, and failed HRESULTs return no array.
Buffer backing safetyNode backing storage is retained and revalidated before invocation. Detached, moved, resized, width-mismatched, non-integral-length, or misaligned storage is rejected; caller output storage is zeroed before the call. Failed HRESULTs never return a partial success-shaped buffer result.
Failure-time stream progressISequentialStream may write a partial count before returning failure. The runtime cleans initialized owned elements exactly, but generated wrappers throw and deliberately discard failure-time partial data rather than returning a success-shaped buffer.
Authoritative count-param detectionRelationships come solely from NativeArrayInfo(CountParamIndex) or an exact cited override registry. Documented overrides cover ISequentialStream::Read/Write, IDiscRecorder::GetRecorderGUID, IOpcSignatureCustomObject::GetXml, and the fixed-capacity/actual-byte IMFAttributes::GetBlob contract. The previous substring/adjacency heuristic remains removed.
Private-data ownership guardActual metadata contains seven declaring GetPrivateData(REFGUID, UINT*, void*) interfaces. Their documentation permits returning an AddRef'd interface set by SetPrivateDataInterface; Direct3D 10 also documents destructive NULL behavior, and DXGI does not mark the data pointer optional. They remain exact, cited fail-closed hazards rather than being projected as leaking Buffer methods.
Atomic multi-interface writesWhen a single generate invocation projects several COM interfaces, every interface is projected into memory first; files (and the com/ index/package barrel) are only written once the whole batch has projected successfully. A later interface's projection failure no longer leaves an earlier interface's files partially written to disk.
Coclass projectionGUID-bearing Classic COM coclasses such as TaskbarList, FileOperation, and FileOpenDialog generate as independently constructible JS classes (new TaskbarList()). Interface wrappers remain non-publicly constructible and register private descriptors for safe projectAs QueryInterface views.
Interface viewsGenerated coclasses expose as(InterfaceClass), tryAs(InterfaceClass), and supports(InterfaceClass). These execute real QueryInterface calls; tryAs returns null only for E_NOINTERFACE, while other errors remain visible.
Safe projection entryprojectAs(value, InterfaceClass) on /com borrows a managed native value or generated wrapper and returns a separately owned QueryInterface wrapper. The target must be a registered generated safe COM interface class; arbitrary raw pointers and unsafe target classes are rejected. Generated modules register descriptors through private /com/unsafe helpers, not application-supplied ABI declarations.
Audio endpoint entryExact IMMDevice::Activate evidence projects activate(InterfaceClass) with an inferred target wrapper, fixed CLSCTX_INPROC_SERVER = 1, and native NULL activation parameters. Exact GetId ownership projects getId(): string and frees the CoTaskMem allocation. These are semantic plans over shared runtime primitives, not per-interface native adapters.
Conservative primary interfaceWindows.Win32 coclass TypeDefs do not carry InterfaceImpl/DefaultAttribute rows. The generator associates only exact metadata naming candidates, constructs the real interface inheritance graph, and selects a primary only when there is one unique most-derived leaf. Multiple unrelated leaves fail closed rather than choosing by numeric suffix.
CommonJS and ESMCOM implementation files use the same CommonJS format as generated WinRT files. com/index.js is the CommonJS barrel, while com/index.mjs is the ESM facade. Package exports provide explicit require and import conditions for the COM barrel and deep imports.
Explicit raw opt-inManual ABI declarations and caller-supplied COM pointers are isolated under @microsoft/dynwinrt/com/unsafe. The default COM facade exports initialization, projectAs, and managed value/layout wrappers—not DynCom, signatures, interfaces, native types, or DynComUnsafe; generated safe bindings continue to fail closed.

Generated package layout

When WinRT and Classic COM are generated together, WinRT remains at the package root and Classic COM uses a domain-specific CommonJS subpackage with an ESM facade:

bindings/
├── index.js
├── index.mjs
├── index.d.ts
├── windows/foundation/Uri.js
├── com/
│   ├── package.json
│   ├── index.js
│   ├── index.mjs
│   ├── index.d.ts
│   └── windows/win32/ui/shell/
│       ├── TaskbarList.js
│       └── ITaskbarList4.js
└── package.json

The root index never re-exports COM symbols. Generate types from different namespaces in one invocation by using fully qualified names:

dynwinrt-codegen generate `
  --winmd "C:\path\to\Windows.winmd;C:\path\to\Windows.Win32.winmd" `
  --class-name Windows.Foundation.Uri,Windows.Win32.UI.Shell.TaskbarList `
  --output .\.winapp\bindings

Generated coclasses follow the WinRT runtime-class convention:

import { TaskbarList, ITaskbarList3, TBPFLAG } from './bindings/com/index.mjs';

const taskbar = new TaskbarList();
taskbar.setProgressState(hwnd, TBPFLAG.TBPF_NORMAL);

const v3 = taskbar.as(ITaskbarList3); // real QueryInterface
v3.release();
taskbar.release();

Separate WinRT and COM invocations may target the same output directory in either order. The generated package manifest is rebuilt from both domains without adding COM exports to the WinRT root.

PR1 raised the generated COM file-ownership manifest com/.dynwinrt-com-manifest.json from version 2 to version 3. Borrowed-copy context effects introduced version 4; typed borrowed-copy recipes now require version 5. Every generated safe class now registers a descriptor through private @microsoft/dynwinrt/com/unsafe helpers. Public projectAs remains on the runtime @microsoft/dynwinrt/com entrypoint, and generated IMMDevice.activate(InterfaceClass) uses that private descriptor registry. Unregistered/unsafe targets and numeric, Buffer, or native RawPtr sources are rejected. Other generated .as(...) paths remain unchanged.

Existing bindings must be deleted and completely regenerated with the matching updated runtime and codegen. Regenerate the complete bindings directory, including all selected roots; in-place cross-version migration, editing the manifest version, and mixing old/new generated classes are not supported. This does not change WinRT semantics or the runtime root API.

winappCli project aliases use #winapp/bindings/com for the COM barrel and canonical deep imports such as #winapp/bindings/com/windows/win32/ui/shell/ITaskbarList3. Existing source that used a flat COM deep import should switch to the canonical namespace path. Standalone COM-only packages also require the explicit com/ prefix; package root imports do not expose COM symbols.

Explicit unsafe/raw opt-in

APIs that cannot be proven from metadata remain absent from generated safe bindings. A caller that independently knows the complete native contract may opt in through the separate @microsoft/dynwinrt/com/unsafe entrypoint:

import {
  DynCom,
  DynComMethodSig,
  DynComUnsafe,
  WinGuid,
} from '@microsoft/dynwinrt/com/unsafe';

const iid = WinGuid.parse('00000000-0000-0000-c000-000000000046');
const raw = DynComUnsafe.registerIUnknownInterface('Example.IRaw', iid)
  .addMethodAt(
    3,
    'ReadPointer',
    new DynComMethodSig()
      .addIn(DynCom.u32Type())
      .addOut(DynComUnsafe.coTaskMemOutputType()),
  );

The signature must explicitly declare every ABI type, direction, vtable slot, count/capacity/actual relationship, return convention, and output cleanup; the runtime applies the architecture-correct COM system calling convention. Available raw output choices deliberately distinguish unclassified borrowed pointer bits, borrowed handles, Release-owned COM pointers, CoTaskMemFree allocations, and SysFreeString BSTRs. An unclassified pointer output is never automatically adopted.

DynComUnsafe.borrowComPointer(bits, iid) treats the supplied pointer as borrowed and obtains a new managed +1 reference through QueryInterface. DynComUnsafe.adoptOwnedComPointer(bits, iid) instead consumes exactly one caller-supplied +1 reference, including on IID-validation failure. Both accept only explicit numeric pointer bits; Buffer backing addresses are rejected to avoid contents-versus-address ambiguity.

This surface is intentionally unsafe: an invalid pointer, IID, slot, calling convention, layout, direction, count relation, or ownership declaration can crash the process or corrupt memory. It does not add inference or a fallback to the default generator, and it remains outside both @microsoft/dynwinrt and @microsoft/dynwinrt/com.

Partially implemented

Problem familySupported subsetRemaining gap
Native pointersPointer width, depth preservation, borrowed pointers, handles, REFIID, known interface outputs, and 22 exact documented borrowed HWND* outputsGeneral nullable/required semantics, arbitrary pointee storage, unregistered HWND outputs, and all allocator contracts
Counted buffersPlain primitive/GUID/enum/POD elements plus exact owning COM-interface, BSTR, VARIANT, and IEnumString PWSTR elements with authoritative relations; input, caller output, actual/fetched length, generated fixed-capacity bytes, exact bounded two-call sizing, and exact CoTaskMem callee outputPROPVARIANT/SAFEARRAY/unknown pointer/resource elements, nested-owning POD, owning callee-allocated outer arrays without exact allocators, undocumented sizing loops, and ANSI output decoding
Native layoutMetadata-driven POD structs plus tagged pointer-input unions with exact x86/x64/ARM64 layout; union fields overlap at offset zero and may contain only safe POD fieldsBy-value/output/nested unions without discriminant contracts, non-default or unknown packing, bitfields, flexible arrays, non-authoritative explicit offsets, and nested BSTR/interface/resource ownership
VARIANTEmpty/null, scalar integer/float/bool, BSTR, UNKNOWN, DISPATCH, supported SAFEARRAY values, and authoritative counted input/output arrays with exact VariantCopy/VariantClear ownershipOptional aggregate defaults, bare aggregate output/InOut, BYREF, DECIMAL/DATE/CY/ERROR/RECORD and other VARTYPEs, Automation replacement, and arrays without exact count/ownership contracts
DISPPARAMS / EXCEPINFOExact IDispatch::Invoke input/output contracts, dedicated JS wrappers, optional null outputs, deferred fill, and failure cleanupOutput/InOut DISPPARAMS, input/InOut EXCEPINFO, nested occurrences, and arbitrary deferred/function-pointer contracts
SAFEARRAYRank 1–8, signed bounds, typed scalar/bool/BSTR/interface/VARIANT elements, SafeArray API validation and cleanupUnsupported element VARTYPEs, rank > 8, untyped arrays whose VARTYPE cannot be proven, and Automation InOut replacement
PROPVARIANTScalar numeric/bool, LPWSTR, CLSID, FILETIME, blob, and supported vectors with PropVariantClearNested VARIANT vectors, streams/interfaces, arrays, clipboard/storage types, BYREF, and unknown VARTYPEs
FORMATETC / STGMEDIUMDedicated target-device-independent TYMED_HGLOBAL values; owned outputs use ReleaseStgMedium; exact IDataObject::GetDataHere evidence permits caller-allocation-preserving InOut; exact IID/slot/shape/fingerprint evidence pins SetData.fRelease=FALSEDVTARGETDEVICE, non-HGLOBAL media, unproven InOut contracts, JavaScript callback implementation, and ownership-transfer input
Audio formatsVariable-length WAVEFORMATEX/WAVEFORMATEXTENSIBLE bytes with validated cbSize; PCM factory; exact shared/exclusive IsFormatSupported output selection; CoTaskMem-owned format outputs; exact nullable device-control Record inputsFormat-specific codec payload interpretation and JavaScript callback implementation
Allocator ownershipCOM Release, BSTR output/replacement and array elements, VARIANT clear, CoTaskMem buffers/PWSTR elements, boxed GUID, retained JS buffersLocalFree, custom allocators, allocator interfaces, unknown ownership
Interface pointersTyped input/output interfaces, QueryInterface, dynamic IID output, and generated multi-interface callback objects with inherited IID aliasesInterface in/out replacement, aggregation, and IInspectable implementation
ApartmentsExplicit initialization, non-agile owner-thread implementations, synchronous same-thread callbacks, and rejection before entering JS on a foreign threadCross-apartment marshaling, GIT/agility handling, and callback dispatch
ActivationIn-process CoCreateInstance and CoGetClassObjectAggregation, arbitrary CLSCTX, and other non-CoCreate factory functions
Direct pointer returnsRuntime signature plus exact IMalloc codegenOther direct pointer returns remain fail-closed without exact ownership and cleanup evidence

Not implemented

  • by-value/output/nested unions without an explicit discriminant and ownership contract;
  • optional or output/InOut aggregate VARIANT, unsupported VARIANT alternatives, BYREF/InOut replacement, and arrays without authoritative count/ownership;
  • DISPPARAMS/EXCEPINFO directions, nesting, or callback shapes outside the exact supported IDispatch::Invoke contract;
  • unsupported PROPVARIANT alternatives and nested ownership;
  • unsupported SAFEARRAY element types/ranks and InOut replacement;
  • BSTR pointer nesting, scalar input BSTR*, callee-allocated outer arrays without exact allocators, and unknown/custom BSTR allocation contracts;
  • DVTARGETDEVICE, non-HGLOBAL FORMATETC/STGMEDIUM alternatives, and FORMATETC/STGMEDIUM callback methods;
  • callback methods containing unmodeled ownership, Automation, union, array, or interface-replacement contracts;
  • cross-thread/apartment marshaling.

Flat Win32 DLL exports and handle cleanup are outside this COM model; they use the separate Win32 contract layer.

Supported ABI surface

CapabilityStatusNotes
IUnknown and IInspectable rootsSupportedUser methods begin at vtable slot 3 or 6 respectively. Full inherited Classic COM slot numbering is preserved.
HRESULT methodsSupportedFailed HRESULTs become errors.
Semantic HRESULT methodsSupportedCanReturnMultipleSuccessValuesAttribute preserves successful values such as S_OK and S_FALSE; failed values still become errors.
Native void returnsSupportedUsed by interfaces such as IMalloc.
Direct scalar returnsSupportedIncludes signed/unsigned integers, floating point values, and enums.
Direct pointer returnsExact-contract supportIMalloc returns opaque allocator-bound values. Other direct pointer returns fail closed until ownership and cleanup are proven.
[in], [out], and [in, out] parametersSupported for modeled typesScalars and validated native POD storage are supported. Other composite in/out types fail generation.
Primitive integer and floating-point typesSupportedi8 through u64, f32, f64, BOOL, and HRESULT.
ISize / USizeSupportedProjected with the target pointer width; verified by an i686 compile check.
GUID values and REFIID/REFGUID pointersSupportedGUID out storage uses the full 16-byte layout.
Signed and unsigned enums/flagsSupportedValues up to unsigned 64-bit are preserved; 64-bit JavaScript values use bigint.
Validated native POD structsSupported subsetExact x86/x64/ARM64 layouts; primitive, enum, GUID, pointer-sized, nested POD, and fixed primitive-array fields; by-value, pointer input, output, and in/out calls. Generated values are branded DynComNativeStruct objects, and output storage is zero-initialized.
Validated native unionsSupported subsetPointer inputs only, with exact architecture layout and an explicit active-field-branded DynComNativeUnion. Output/by-value/nested ownership shapes fail closed.
VARIANTSupported subsetDedicated DynComVariant; supported tags are VT_EMPTY, VT_NULL, I1/UI1/I2/UI2/I4/UI4/I8/UI8/INT/UINT, R4/R8, BOOL, BSTR, UNKNOWN, DISPATCH, and arrays of supported SAFEARRAY elements. Pointer contracts remain distinct from required input-only by-value aggregates.
SAFEARRAYSupported subsetDynComSafeArray preserves rank/bounds and validates VARTYPE and element width through SafeArray APIs. Supported elements are the scalar integer/float family, VARIANT_BOOL, BSTR, IUnknown, IDispatch, and VARIANT.
PROPVARIANTSupported subsetDynComPropVariant supports the scalar family, LPWSTR, CLSID, FILETIME, BLOB, and vectors of numeric/bool/string/GUID/FILETIME elements.
FORMATETC / STGMEDIUMSupported HGLOBAL subsetDynComFormatEtc.hglobal() represents one exact DVASPECT with ptd == NULL; DynComStgMedium.hglobal() copies bytes into call-local movable HGLOBAL storage. Outputs are copied before ReleaseStgMedium, GetDataHere rejects replacement of caller-owned storage, and generated IDataObject::SetData and IOleCache::SetData (including IOleCache2) fix fRelease to FALSE.
WAVEFORMATEX / WAVEFORMATEXTENSIBLESupportedDynComAudioFormat validates the packed 18-byte header plus exact cbSize extension, provides a PCM factory and field accessors, and preserves unknown codec extension bytes. Exact contracts pass IsFormatSupported.ppClosestMatch only in shared mode and free GetMixFormat/current-engine-format outputs with CoTaskMemFree. The two device-control Record methods accept an explicit format or null for the device default.
DISPPARAMS / EXCEPINFOSupported for IDispatch::InvokeDynComDispatchParams accepts natural-order DynComVariant[] plus optional named DISPIDs. DynComExcepInfo exposes code/source/description/helpFile/helpContext/scode. Optional Invoke outputs pass native null when not requested.
Typed interface parameters and outputsSupportedInterface outputs carry an owned COM reference.
Opaque pointers and handle-shaped typedefsSupported with limitsThey are pointer values, not COM objects. Cleanup remains type-specific.
Borrowed HWND outputsSupported only for exact registered declarationsQualified HWND* Out metadata and Microsoft lifetime evidence must match exactly. JavaScript returns the natural numeric handle alias; runtime output storage is pointer-sized and has no cleanup.
NUL-terminated string pointer inputsSupportedCallers pass a NUL-terminated Buffer or a borrowed numeric pointer.
Caller-owned UTF-16 output buffersSupported for recognized shapesThe generator allocates and decodes the buffer when metadata identifies the count parameter.
Typed counted input buffersSupported for validated plain elementsGenerated wrappers accept Buffer or TypedArray storage and derive byte/element counts from its exact backing length.
Caller-owned typed output buffersSupported for validated plain elementsCapacity is derived from supplied backing storage; separate or in/out actual lengths trim the returned Buffer to the initialized range.
Generated fixed-capacity byte outputSupported only for registered documented shapesIMFAttributes::GetBlob(guidKey, capacity) allocates exclusive storage in the runtime; capacity is bounded to the projected Buffer limit and native counts remain hidden.
Exact two-call sizingSupported only for registered documented shapesGenerated wrappers start with the documented null/zero query and retry at most twice. Continued size races fail explicitly.
Callee-allocated typed buffersSupported for plain elements with known CoTaskMem ownershipThe runtime copies the exact count, calls CoTaskMemFree once, and returns an owned Node Buffer.
Callee-allocated PWSTR / PSTR outputsSupportedGenerated code decodes and frees CoTaskMem storage.
By-value [in] BSTRSupportedGenerated JavaScript accepts string (or null only for metadata-proven optional input). The runtime creates a uniquely owned call-local SysAllocStringLen allocation, preserving embedded NUL and exact UTF-16 length; the callee only borrows it.
Scalar [out] BSTR*SupportedGenerated code converts the BSTR and releases it with SysFreeString.
Required scalar [in, out] BSTR* replacementSupportedGenerated JavaScript accepts and returns string. A boxed call-local slot owns the current BSTR; the original JavaScript string is immutable, and the final unchanged/replaced/null slot is cleaned or transferred exactly once on every HRESULT path.
Counted BSTR arraysSupported subsetExact input and caller-output count/capacity/actual contracts project as string[]; call-local input elements and every initialized output slot use SysFreeString exactly once. Callee-allocated outer arrays, unsupported pointer nesting, and unknown allocators fail closed.
HSTRING inputs and scalar outputsSupportedJavaScript strings are converted to owning HSTRING values; returned HSTRING values are decoded and released automatically.
Referenced interface typesSupported when IID metadata is loadedMissing external definitions fail closed and direct callers to pass the defining winmd with --ref.
Dynamic-IID void** outputsSupported for explicit required REFIID shapesThe method must return ordinary HRESULT and contain exactly one required const GUID* named iid/riid plus one required mutable void**/Object** output with +1 COM ownership. Their explicit parameter indices may be non-adjacent/non-terminal. Optional, duplicate, array, FreeWith, InOut, by-value/mutable/deeper GUID, and wrong-depth output shapes fail closed.
Typed endpoint activationExact-contract supportIMMDevice.activate(InterfaceClass) accepts only registered generated safe classes for the documented null-parameter IID subset below. The caller cannot supply CLSCTX flags or an activation-parameter pointer.
Explicit apartment initializationSupportedinitializeCom() never silently chooses an apartment for the caller.

IMMDevice is safe-complete with exact IID d666063f-1587-4e43-81f1-b948e807363f, slot-3 Activate, and full-method fingerprint evidence. Its activation targets are limited to IAudioClient, IAudioEndpointVolume, IAudioMeterInformation, IAudioSessionManager, and IAudioSessionManager2; other/custom targets, asynchronous activation, and loopback or other parameterized activation remain outside this safe subset. The separate slot-5 GetId JSON ownership entry supplies its CoTaskMem string contract. Both entries belong to com.ownership.v1; see the contract evidence registry. The audio endpoint example uses projectAs and activate(IAudioClient) without exposing raw pointers. Existing safe IAudioClient, IAudioClient2, and IAudioClient3 format contracts remain unchanged.

IMDSPDeviceControl::Record and IWMDMDeviceControl::Record have exact nullable audio-input contracts. Their declarations accept DynComAudioFormat | null, the COM signature retains input nullability, and the executor passes a native NULL without constructing a dummy format. Required inputs such as IAudioClient::Initialize and IsFormatSupported remain non-nullable; unknown or drifted Record evidence fails generation.

IDataObject::GetCanonicalFormatEtc has a separate result contract. For S_OK, the runtime ignores the native output's tymed and retains the caller's supported transfer medium while decoding the remaining canonical fields. For DATA_S_SAMEFORMATETC, the returned value is a copy of the input; the unused output is not decoded or adopted. The native HRESULT is preserved in both cases. Other FORMATETC outputs retain the strict HGLOBAL validator, and unsupported target-device output is cleaned and rejected.

STGMEDIUM InOut is not a type-wide capability. Its safe projection requires exact caller-allocation-preservation evidence, currently registered only for IDataObject::GetDataHere. The callee must not resize or replace the HGLOBAL, and the caller retains cleanup responsibility. IWiaDataTransfer::idtGetData has a separate property-selected file-transfer and filename-output contract; it is not safe-generated. Its unsafe/raw capabilities remain subject to the existing layout and contract restrictions.

The generator emits native POD storage only after every architecture-specific layout fact has been validated. A Buffer in this path represents the struct's backing bytes/address; it is not interpreted as pointer-width handle bits.

Parameterized and async interfaces, delegates, and native arrays outside the explicit count and element-ownership models remain fail-closed until the COM projection can compute their complete IID, callback, count, and ownership contracts. They must never fall back to bigint | Buffer.

Unsupported types and shapes

The generator fails closed for unsupported signatures instead of emitting a plausible but memory-unsafe binding.

The native type rows below come from real signatures in Windows.Win32.winmd, including the 30-interface survey, plus the exact fail-closed diagnostics produced by the current generator. The policy rows describe known runtime/public-API boundaries. This is not an exhaustive scan of every type in the 24 MB metadata file.

Type or shapeAffected common APIsWhy it is unsupportedBasis
Unsupported VARIANT alternatives, aggregate directions, and BYREF/InOutAutomation APIsRequired input-only by-value VARIANT is supported. Optional aggregate defaults, bare aggregate output/InOut, DATE, DECIMAL, CY, ERROR, RECORD, unsupported flags, and every BYREF/InOut combination fail closed until their lifetime/replacement contracts are proven.Runtime validation + Win32 winmd signatures
Unsupported DISPPARAMS / EXCEPINFO shapesAutomation APIs outside exact IDispatch::InvokeOutput/InOut DISPPARAMS, input/InOut EXCEPINFO, nested compounds, reinstalled deferred callbacks, and unrelated function-pointer contracts fail closed.Runtime validation + Win32 winmd signature
Unsupported PROPVARIANT alternativesProperty SystemStreams/interfaces, arrays, clipboard/storage alternatives, nested VT_VECTOR|VT_VARIANT, BYREF, and unknown combinations are rejected.Runtime validation + Win32 winmd signature
Native structs with nested owned pointers outside dedicated contractsStorage and Shell APIsSTATSTG has a dedicated output-only model that adopts and frees its CoTaskMem name on every success and failure path. Arbitrary nested pointer structs still fail closed.Runtime ownership tests + Win32 winmd signature
Unsupported SAFEARRAY shapesAutomation and Office-style COM APIsExact declaration-registry entries support documented VT_I4, VT_UI1, VT_UI4, VT_R8, VT_BSTR, VT_VARIANT, and VT_UNKNOWN plus an exact interface IID. Unknown VARTYPE, signature drift, input SAFEARRAY**, InOut replacement, unsupported records/dispatch contracts, rank > 8, inconsistent bounds/length/element width, and unproven nullable outputs are rejected.Exact Microsoft citations + SafeArray API validation + Win32 winmd signatures
Unsupported FORMATETC / STGMEDIUM alternativesIDataObject, clipboard, drag-and-dropThe dedicated model currently accepts only target-device-independent TYMED_HGLOBAL. DVTARGETDEVICE, GDI, metafile, file, stream, storage, and callback shapes remain closed until their own layout and ownership contracts exist.Win32 winmd + Microsoft FORMATETC/STGMEDIUM contracts
Untagged/by-value/output/nested unions, bitfields, flexible arrays, and nested owned-resource structsSTRRET, BINDPTR, audio/media formatsDedicated STGMEDIUM handling does not generalize to arbitrary unions. Tagged pointer-input unions support only safely POD fields. Missing discriminants, nested unions, BSTR/interfaces/resources, bitfields, and flexible tails fail closed.Win32 winmd + codegen diagnostics
Unknown packing or non-authoritative explicit offsetsExplicit/packed native recordsExact x86/x64/ARM64 size, alignment, and field offsets are mandatory. Missing facts fail closed rather than assuming the host compiler's defaults.Layout validation policy
Writable caller-sized native arrays outside the modeled subsetOwning, pointer-element, native-POD, or incompletely described arraysIDispatch::GetIDsOfNames is supported in the complete interface when one metadata count unambiguously groups borrowed string pointers with plain scalar output. Missing count direction, element layout/ownership, or an unambiguous group still fails closed.Win32 winmd NativeArrayInfo + semantic validation
Unsupported BSTR pointer nesting, replacement arrays, or callee-allocated outer arraysAutomation collection APIsExact counted input/caller-output BSTR arrays are supported; deeper/replacement shapes still require authoritative outer allocation and per-element contracts.Win32 winmd signature + ownership analysis
Caller-owned ANSI output buffersPSTR output-buffer APIsSafe sizing and decoding are not yet projected.Win32 winmd signature + projection limitation
Private-data bytes or interface pointerIDXGIObject, ID3D10DeviceChild, ID3D10Device, ID3D11DeviceChild, ID3D11Device, ID3D12Object, and IDMLObject GetPrivateDataThe same GUID-keyed method may return ordinary bytes or an AddRef'd interface pointer. A Buffer projection would lose the interface ownership transfer; Direct3D 10 NULL calls are destructive, and DXGI's data parameter is required.Exact Win32 winmd identities + Microsoft method documentation
Untyped output pointers without allocator/ownershipUnrelated void* outputsThe runtime cannot infer whether the result is borrowed, COM-owned, CoTaskMem, or another allocator. Audio-format outputs are supported only for exact pinned methods.Win32 winmd + codegen diagnostics
Interface [in, out] ownershipGeneric typed IFoo** InOut parametersReplacing an existing interface pointer requires explicit release/AddRef transfer semantics. IWbemServices::OpenNamespace is not in this category: exact SDK evidence corrects its two flags to Out.Win32 winmd + codegen diagnostic
Callback methods outside the validated implementation subsetAutomation providers, custom marshaling, and resource-owning callbacksThe dynamic backend supports broad scalar/string/POD/buffer ABI shapes and multi-interface inheritance, but VARIANT/SAFEARRAY/PROPVARIANT callbacks, untagged unions, unknown pointers/allocators, interface replacement, and custom marshal contracts still fail the whole interface closed.Runtime/codegen validation boundary
COM aggregationIClassFactory::CreateInstance with pUnkOuterThe public activation helper always creates a non-aggregated in-process object.Runtime/public-API boundary
General out-of-process activation controlsCustom CLSCTX scenariosThe unsafe runtime's DynCom.coCreateInstance() currently uses CLSCTX_INPROC_SERVER.Runtime/public-API boundary
Flat Win32 DLL exportsCreateFile, registry functions, GDI, etc.These are not COM interfaces; supported exports use the separate Win32 DLL-export/handle model.Architecture boundary

Consequently, IDataObject now generates completely for the closed HGLOBAL subset. Unsupported target-device and storage-medium inputs cannot be constructed, while an unsupported native output is cleaned and rejected during decoding. IPropertyStore and IDispatch are complete; derived Automation interfaces still validate all of their additional methods independently.

Complete-interface census after by-value VARIANT

A temporary full census against Microsoft.Windows.SDK.Win32Metadata 71.0.14-preview parsed 7,944 interfaces. The temporary census code and generated output are not retained in the repository.

ResultBeforeAfterDelta
Complete interfaces4,7735,188+415
Incomplete interfaces3,1712,756-415

All 415 interfaces whose first blocker was a bare required input VARIANT became complete; no previously complete interface regressed. The remaining first-blocker categories are:

CategoryInterfaces
Ownership/cleanup1,113
Arrays/buffers666
Native layout437
Other validated contracts252
SAFEARRAY146
Pointer semantics142

The largest exact first blockers are the cited GetPrivateData ownership hazards: ID3D12Object (82), ID3D11DeviceChild (58), IDXGIObject (39), and ID3D10DeviceChild (24), followed by PROPVARIANT InOut GetItem contracts (19).

Automation remains intentionally partial. Across incomplete interfaces, 143 first fail on an unsupported SAFEARRAY element and 54 first fail on VARIANT/PROPVARIANT/SAFEARRAY BYREF/InOut replacement. Eleven first fail on a bare VARIANT aggregate in a non-input direction, twelve on the analogous PROPVARIANT shape, and IEnumVARIANT::Next remains one caller-sized VARIANT array blocker. Existing supported pointer-shaped VARIANT inputs and owned outputs are unchanged.

Real metadata regressions lock two representative APIs:

IAccessible.accSelect(flagsSelect: number, varChild: DynComVariant): void;
IUIAutomation.createPropertyCondition(
  propertyId: UIA_PROPERTY_ID,
  value: DynComVariant
): DynWinRtValue;

IAccessible (IID 618736e0-3c3d-11cf-810c-00aa00389b71) now generates completely, including accSelect at slot 21. IUIAutomation::CreatePropertyCondition (IID 30cbe57d-d9d0-452a-ab13-7ac5ac4825ee) projects in isolation at slot 23; the complete interface now passes its exact runtime-ID SAFEARRAY methods and stops later at ElementFromPoint.pt because POINT has not reached the validated native-layout projection.

Complete-interface census after safe dynamic-IID generalization

The same temporary census against Microsoft.Windows.SDK.Win32Metadata 71.0.14-preview parsed 7,944 interfaces. The temporary census code and generated output are not retained.

ResultStaged baselineGeneralizedNet delta
Complete interfaces5,1885,200+12
Incomplete interfaces2,7562,744-12

Thirteen interfaces became complete because a required iid/riid and its owned void** result may now be non-adjacent or non-terminal:

IDCompositionSurface, IDCompositionTexture, IXpsDocumentConsumer, IAMGraphStreams, IPSFactoryBuffer, IClassFactory2, IRemoteSystemAdditionalInfoProvider, ISearchLanguageSupport, ICompositionDrawingSurfaceInterop, ICompositionDrawingSurfaceInterop2, ICompositionTextureInterop, ISurfaceImageSourceNativeWithD2D, and ICustomDestinationList.

INetCfg intentionally moved from complete to incomplete: QueryNetCfgClass.ppvObject is optional in metadata, so it no longer receives owned-COM adoption. No required-output interface regressed.

The remaining first-blocker categories are:

CategoryInterfaces
Ownership/cleanup1,101
Arrays/buffers666
Native layout436
Other validated contracts253
SAFEARRAY146
Pointer semantics142

The top exact blockers remain the cited GetPrivateData ownership hazards: ID3D12Object (82), ID3D11DeviceChild (58), IDXGIObject (39), and ID3D10DeviceChild (24), followed by PROPVARIANT InOut GetItem contracts (19).

Forty-three interfaces (67 methods) now contain an ambiguous dynamic-IID-like shape: 31 contain InOut/interface-array contracts, nine use optional void** outputs, two combine the otherwise valid pair with hidden counted buffers, and one (IEnumObjects::Next) marks the interface output with NativeArrayInfo. IDxcResult::GetOutput, IDirectManipulationViewport2::GetTag, and INetCfg::QueryNetCfgClass are representative optional-output blockers; IMFTopologyServiceLookup::LookupService remains blocked by its InOut multi-interface count.

Complete-interface census after BSTR normalization and replacement

The executable BSTR census used Microsoft.Windows.SDK.Win32Metadata 71.0.14-preview and selected 7,930 currently eligible interfaces (IUnknown-rooted interfaces plus the existing *Interop selection). This is deliberately narrower than the 7,944 raw interfaces reported by older census sections; before/after counts below use the same 7,930-interface selector. Temporary census code and output are not retained.

The deduplicated declared BSTR shapes were:

Raw metadata shapeOccurrences
Required by-value input4,201
Optional by-value input27
Scalar BSTR* input3
Input BSTR array7
Required scalar BSTR* InOut323
Optional scalar BSTR* InOut4
BSTR** InOut2
Invalid by-value Out5
Required scalar BSTR* Out3,099
Out BSTR array5
Optional scalar BSTR* Out27
BSTR** Out6
BSTR** Out array1

The four optional metadata-InOut occurrences are documentation-defined Out parameters: IPhotoAcquireDeviceSelectionDialog::DoModal.pbstrDeviceId and the three IDiscRecorder::GetDisplayNames strings. The exact Microsoft API declarations are cited in the semantic override registry. No nullable generic BSTR replacement contract is inferred.

Before this phase, 120 interfaces first failed on a scalar BSTR replacement method; 38 were inherited descendants. The complete-interface result is:

ResultBeforeAfterNet delta
Complete interfaces5,1885,289+101
Incomplete interfaces2,7422,641-101

The simulated bucket was larger because 18 replacement candidates expose another unsupported contract after BSTR replacement becomes valid. IPhotoAcquireDeviceSelectionDialog is one example: its BSTR is correctly normalized to Out, but pnDeviceType remains an optional scalar InOut contract without a safe nullable lowering. IDiscRecorder becomes complete and returns its three display-name strings deterministically. Separately, IFileSearchBand deliberately moves from complete to incomplete: its scalar input BSTR* had previously degraded to a raw pointer even though the pointee allocation/borrow contract is not proven.

The largest remaining exact first-error labels in this selector are unsupported COM semantic (215), invalid COM contract (52), PROPVARIANT GetItem.pValue (19), GetWindow.phwnd (14), MovedReferences (11), and GetKernelConnectionOptions (9).

The supported ownership proof follows the Automation rules documented by Memory Management Rules and SysAllocStringLen:

  • by-value input: the caller allocates/frees; the callee borrows;
  • Out: the callee allocates; the caller owns and frees the result;
  • InOut: the caller supplies the initial BSTR, the callee may free/replace it, and the caller frees the valid final slot;
  • on failure, the InOut slot must remain unchanged or contain another safely cleanable value (including null).

Runtime storage is a boxed, uniquely owned call-local BSTR slot. Allocation uses explicit UTF-16 lengths, never NUL-terminated inference. On success the final slot transfers once to takeBstr; on failure RAII frees the valid final slot. Fake-vtable tests cover embedded NUL input/output, unchanged/replaced/null success and failure, original-string immutability, and exact allocation/free counts.

At the end of that scalar-BSTR phase, before counted owning elements were implemented, the following remained excluded:

  • three scalar input BSTR* contracts (IFileSearchBand::SetSearchParameters and the two VSS SaveAsXML methods);
  • all 13 native BSTR pointer-array occurrences because initialized-range element cleanup is incomplete; these are distinct from exact SAFEARRAY(BSTR) declarations, which are covered by the registry below;
  • all nine BSTR** occurrences because pointer nesting/array ownership is not proven;
  • five invalid by-value Out occurrences; and
  • unknown/custom allocators or any BSTR shape not paired with exact Automation ownership evidence.

Complete-interface census after exact SAFEARRAY subtype evidence

The executable census used Microsoft.Windows.SDK.Win32Metadata 71.0.14-preview and the same 7,930 eligible-interface selector as the BSTR phase. It deduplicated declarations at their declaring interface, so inherited methods were not counted repeatedly. Temporary census code and output are not retained.

The metadata contains 239 directly declared SAFEARRAY parameters. The exact registry covers 209 declarations and leaves 30 direct declarations unsupported. Every registry row keys the declaring namespace, interface IID, method name, vtable slot, parameter index/name, and complete raw method shape. Each row also records element VARTYPE, exact element IID when applicable, borrowed-input or owned-output semantics, a reason, and a Microsoft citation. An identity or raw-shape mismatch is a contract error, not a generic fallback.

Documented element VARTYPERegistry entries
VT_I425
VT_UI125
VT_UI43
VT_R83
VT_BSTR40
VT_VARIANT85
VT_UNKNOWN plus exact interface IID28
Total209

Of these entries, 69 are borrowed inputs and 140 are owned outputs. Major families include UI Automation (61), File Server Resource Manager (50), Mobile Broadband (25), IMAPI (25), Performance Logs and Alerts (20), Component Services (9), Remote Desktop (8), tuner APIs (5), WMI (3), and Camera UI (1).

Representative evidence:

  • UI Automation runtime IDs are documented SAFEARRAY(int) (VT_I4); text selection/visible ranges are ITextRangeProvider* arrays with IID 5347ad7b-c355-46f8-aff5-909033582f63.
  • Connected-client snapshots are documented IUIAutomationClientInfo* arrays with IID b2e8a3f1-4c5d-4e7a-8f6b-3d2e1c9a0b8f.
  • PLA BSTR properties use the exact method sections in MS-PLA; this covers alert thresholds, API tracing filters, configuration files and queries, collector-set keywords, and performance counters.
  • ITraceDataProvider::FilterData is a documented byte array (VT_UI1).
  • Registered workspace extensions and Camera UI selected-item paths are documented BSTR arrays by GetRegisteredFileExtensions and GetSelectedItems.

The current nullable output set is ITextProvider::GetSelection, IRawElementProviderFragment::GetEmbeddedFragmentRoots plus IRawElementProviderFragment::GetRuntimeId and IDragProvider::GetGrabbedItems. Their exact evidence permits a successful contained SAFEARRAY* to be null; the receiving SAFEARRAY** cell is still required. These four methods project as DynComSafeArray | null; GetVisibleRanges and all other registered outputs remain required. The contract evidence registry stores these existing allowances as structured, cited nullability fields in safearrays.json, without method-name branches or new support.

The complete-interface result is:

ResultBeforeAfterNet delta
Complete interfaces5,2895,399+110
Incomplete interfaces2,6412,531-110
SAFEARRAY first blockers14717-130

The first-blocker decrease is larger than the complete-interface gain because 20 interfaces advance to another unsupported contract. The remaining 17 SAFEARRAY first blockers are:

  • Mobile Broadband provisioned-context/device-service arrays (2);
  • WinHTTP input SAFEARRAY** event data (1);
  • Component Services module/query arrays, including inherited catalog amplification (3);
  • IIS provider configuration records (1);
  • Task Scheduler and Transaction Server metadata-InOut arrays (2);
  • IME InOut arrays (1);
  • Tablet PC nested/interface arrays (2);
  • XAML Diagnostics record arrays inherited through three interfaces (3); and
  • MSHTML event-listener and document-write arrays whose exact Automation VARTYPE contracts are not consistently documented (2).

Generated JavaScript keeps every supported result as DynComSafeArray, not a natural array, so VARTYPE, exact interface IID, rank, and signed lower bounds remain observable. Generated signatures use DynCom.safeArrayType(kind, iid?, nullable?); owned results transfer through DynCom.takeSafeArray or, for the four proven nullable methods, DynCom.takeNullableSafeArray. DynComSafeArray.interface creates VT_UNKNOWN arrays with SafeArrayCreateEx; its interfaceIid, bounds, elementType, and conversion methods preserve identity and shape.

Input descriptors are borrowed for the native call under a per-array lock and remain owned by the caller. Output SAFEARRAY** storage starts null, adopts the callee result once, validates VARTYPE, rank, bounds, element width, data alignment, and every non-null typed-interface element with QueryInterface, and calls SafeArrayDestroy on every mismatch or failed conversion. An exact descriptor IID is accepted only when it matches the documented IID. A descriptor carrying IID_IUnknown, or no FADF_HAVEIID identity at all, is accepted only after every element passes that exact query; this is required by stock UI Automation providers such as Microsoft Terminal, which creates its documented text-range results with SafeArrayCreateVector(VT_UNKNOWN, ...). SafeArrayGetIID documents the missing-FADF_HAVEIID E_INVALIDARG result. The wrapper records the proven semantic IID after validation. Typed interface inputs are QueryInterface-validated before creation; SafeArray element insertion/removal provides the corresponding AddRef/Release behavior. There is no second dispatch backend.

Counted arrays with owning elements

An executable census against Microsoft.Windows.SDK.Win32Metadata 71.0.14-preview deduplicated declarations at their declaring interface. It found 13 BSTR arrays (7 input, 5 caller output, 1 callee allocated), 262 interface arrays (113 input, 142 caller output, 7 callee allocated), and 13 VARIANT arrays (3 input, 10 caller output). The caller-output totals include in/out count contracts. Exact IEnum*::Next shapes occur at slots 3, 4, and 8; inherited declarations are not counted again.

Supported generated surfaces use string[], DynComVariant[], or nominal generated interface-wrapper arrays. Input storage is call-local: BSTR uses SysAllocStringLen, including embedded NULs; VARIANT uses contiguous VariantCopy storage; interface values are queried for the exact element IID and borrowed while those wrappers remain alive. Shared authoritative counts may describe parallel input/output arrays, and signed count ABIs are accepted only through checked non-negative conversions.

Caller output storage is zeroed across the full capacity; VARIANT slots are additionally VariantInit-initialized. Successful calls validate actual/fetched <= capacity, transfer only that initialized range, and clean unused slots. Failed HRESULTs return no partial values and scan the bounded capacity. Interface slots release each remaining non-null +1; BSTR slots use SysFreeString; VARIANT slots use VariantClear; IEnumString PWSTR slots use CoTaskMemFree. Validation and conversion are transactional, including null interface holes and unsupported/BYREF VARIANT tags.

IEnumVARIANT (IID 00020404-0000-0000-c000-000000000046) and IEnumString (IID 00000101-0000-0000-c000-000000000046) follow their exact element ABIs and preserve S_OK/S_FALSE. pceltFetched is nullable only where the exact standard metadata contract permits it and only for a request of one element. Incomplete element interfaces receive an opaque nominal wrapper containing no projected methods, rather than weakening the owning array API or projecting unsafe members.

Enumerator projection is keyed by exact namespace, interface IID, and element type; an IEnum* name is not ownership evidence. The exact enumerator registry contains 97 declarations, including IEnumUnknown, IEnumVdsObject, IEnumEventObject, and IEnumITfCompositionView. Likewise, only NUL-terminated *STR aliases are automatic strings. PWCHAR, PCWCHAR, LPWCH, LPCWCH, LPCH, and LPCCH require an explicit counted-character contract.

The complete-interface census moved from 5,399 to 5,531 of 7,930 eligible interfaces: +132 complete and -132 incomplete. Seven owning-array first blockers remain, all with unknown element semantics: XML issuer pointer lists, DirectWrite target-family pointer lists, two debugger handle arrays, Shell PIDL arrays, Text Services category arrays, and Text Services property arrays. The broader top blockers remain untyped GetPrivateData ownership contracts, PROPVARIANT replacement/InOut contracts, incomplete native layouts, and unknown pointer/allocator semantics.

Excluded shapes remain fail closed: owning callee-allocated outer arrays without an exact outer allocator and per-element contract; BSTR/COM T**/T*** arrays without exact allocation evidence; unknown pointer, handle, PIDL, or nested-owning elements; unsupported VARIANT tags/BYREF; and any count/capacity/actual/fetched relationship that cannot be proven.

Borrowed HWND outputs and the final complete-interface census

The final safety phase used Microsoft.Windows.SDK.Win32Metadata 71.0.14-preview. The literal generator census contains 7,929 externally addressable Classic COM interface identities. This is one fewer than older 7,930-interface census sections because the final selector excludes the IID-less Windows.Win32.UI.Controls.RichEdit.ITextHost2; its HWND declaration is still included in the raw audit below.

An HWND output is never accepted merely because its native type is HWND. Each supported output must match an exact evidence-registry entry by declaring namespace/interface/IID, method, absolute vtable slot, parameter count, parameter index/name, and optionality. The raw contract must also be an ordinary HRESULT method with a required mutable [out] qualified Windows.Win32.Foundation.HWND*, pointer depth one, and no FreeWith, array, SAFEARRAY, or InOut semantics. Any registry or signature drift fails before generic projection.

The 22 accepted declarations are:

DeclarationIIDSlot / parameterMicrosoft ownership evidence
IWiaAppErrorHandler::GetWindow6c16186c-d0a6-400c-80f4-d26986a0e7343 / 0: phwndReturns the existing WIA error-handler dialog HWND; it may be null and remains owned by the handler.
IPhotoProgressDialog::GetWindow00f246f9-0750-4f08-9381-2cd8e906a4ae4 / 0: phwndProgressDialogRetrieves the progress dialog box handle; it does not create or transfer the dialog.
IOverlay::GetWindowHandle56a868a1-0ad4-11ce-b03a-0020af0ba7708 / 0: pHwndRetrieves the existing clipping window associated with the overlay.
IMSVidCtl::get_Windowb0edf162-910a-11d2-b632-00c04f79498e15 / 0: phwndRetrieves the existing video control window.
IMSVidRect::get_HWnd7f5000a6-a440-47ca-8acc-c0e75531a2c215 / 0: HWndValRetrieves the window represented by the existing video rectangle.
IMFPMediaPlayer::GetVideoWindowa714590a-58af-430a-85bf-44f5ec838d8531 / 0: phwndVideoRetrieves the media player's current video window.
IMFVideoDisplayControl::GetVideoWindowa490b1e4-ab84-4d31-a1b2-181e03b1077a10 / 0: phwndVideoRetrieves the video window previously set on the display control.
IConsole::GetMainWindow43136eb1-d36c-11cf-adbc-00aa00a8003312 / 0: phwndRetrieves MMC's existing main frame window.
IOleWindow::GetWindow00000114-0000-0000-c000-0000000000463 / 0: phwndRetrieves an existing participant window; ownership remains with the participant that created it.
ICoreWindowInterop::get_WindowHandle45d64a29-a63e-4cb6-b498-5781d298cb4f3 / 0: hwndGets the HWND of the existing CoreWindow.
IShareWindowCommandEventArgsInterop::GetWindow6571a721-643d-43d4-aca4-6b6f5f30f1ad3 / 0: valueGets the window carried by the event arguments.
IDesktopWindowXamlSourceNative::get_WindowHandle3cbcf1bf-2f76-4e9c-96ab-e84b379725544 / 0: hWndGets the existing parent UI-element HWND.
IUpdateInstaller::get_ParentHwnd7b929c68-ccdc-4226-96b1-8724600b54c211 / 0: retvalRetrieves the existing configured parent window.
IUIAutomationElement::get_CachedNativeWindowHandled22108aa-8ac5-49a5-837b-37bbb3d7591e68 / 0: retValRetrieves the cached native window handle of the existing element.
IUIAutomationElement::get_CurrentNativeWindowHandled22108aa-8ac5-49a5-837b-37bbb3d7591e36 / 0: retValRetrieves the current native window handle of the existing element.
ICredentialProviderCredentialEvents::OnCreatingWindowfa6fa76b-66b7-4b11-95f1-86171118e81612 / 0: phwndOwnerReturns the Credential UI or Logon UI parent HWND, which providers borrow when parenting dialogs.
ILaunchSourceViewSizePreference::GetSourceViewToPositione5aa01f7-1fb8-4830-8720-4e6734cbd5f33 / 0: hwndGets the existing source application window.
IFileIsInUse::GetSwitchToHWND64a1cbf0-3a1a-4461-9158-3769696939506 / 0: phwndRetrieves the existing application window to switch to.
IPreviewHandler::QueryFocus8895b1c6-b41f-4c1c-a562-0d564250836f8 / 0: phwndReturns the HWND observed by GetFocus.
ITextInputPanel::get_AttachedEditWindow6b6a65a5-6af3-46c2-b6ea-56cd1f80df713 / 0: AttachedEditWindowRetrieves the edit window already attached to the text input panel.
ITfContextOwner::GetWndaa80e80c-2021-11d2-93e0-0060b067b86e7 / 0: phwndRetrieves the existing owner window associated with the text context.
ITfContextView::GetWnd2433bf8e-0f9b-435c-ba2c-180611978c306 / 0: phwndRetrieves the existing window represented by the text context view.

At runtime, a borrowed HWND result uses zeroed pointer-width output storage and PointerOutputKind::None, with no Release, DestroyWindow, or other cleanup on success or failure. JavaScript converts the pointer bits with DynCom.asPointerBigint() and exposes the existing natural HWND = bigint | number alias; null is 0n. It never exposes a Buffer whose contents could be confused with its address.

Exact canonical IUnknown (00000000-0000-0000-c000-000000000046) elements in owning counted/enumerator arrays now project directly as DynWinRtValue[]. The runtime's existing per-element +1 adoption and Release cleanup are unchanged; only wrapper rendering changes. No IUnknown.js file or import is generated, and unresolved non-IUnknown interfaces remain nominal and fail closed as before. Real metadata regressions cover IEnumUnknown, IEnumVdsObject, and IEnumEventObject.

Complete HWND declaration audit

The raw metadata census found 58 unique declaring HWND-output parameters. The 22 entries above are the only ones admitted to the borrowed registry. Every other declaration remains excluded, even where documentation suggests an observed/borrowed handle, because the final target was already met or the complete method/interface contains another unmodeled contract.

Declaration (IID)Slot / parameter / shapeDocumentationClassification
IDirectDrawClipper::GetHWnd (6c14db85-a733-11ce-a521-0020af0be560)4 / 0: param0 / HWND* InOutMicrosoftGetter-like, but metadata is InOut; excluded before borrowed-output projection.
IDXGIFactory::GetWindowAssociation (7b7166ec-21c7-44ae-b21a-c9ae321ae369)9 / 0: pWindowHandle / HWND* OutMicrosoftDocumented borrowed identity; not registered because inherited DXGI interfaces remain blocked by GetPrivateData ownership.
IDXGISwapChain1::GetHwnd (790a45f7-0d42-4876-983a-0a55cfe6f4aa)20 / 0: pHwnd / HWND* OutMicrosoftDocumented borrowed identity; not registered because the complete DXGI interface remains blocked elsewhere.
IAMDirectSound::GetFocusWindow (546f4260-d53e-11cf-b3f0-00aa003761c5)10 / 0: param0 / HWND* InOutMicrosoftInOut contract; excluded.
IFullScreenVideo::GetMessageDrain (dd1d7110-7836-11cf-bf47-00aa0055595a)12 / 0: hwnd / HWND* OutNo attached Microsoft documentationOwnership/lifetime not proven; excluded.
IFullScreenVideoEx::GetAcceleratorTable (53479470-f1dd-11cf-bc42-00aa00ac74f6)21 / 0: phwnd / HWND* OutMicrosoftHWND is observed, but the method also returns an accelerator-table resource; excluded pending its complete ownership model.
IOverlay::GetWindowHandle (56a868a1-0ad4-11ce-b03a-0020af0ba770)8 / 0: pHwnd / HWND* OutMicrosoftRegistered exact borrowed identity; another interface contract still blocks completeness, so this row adds no complete interface.
IMSVidCtl::get_Window (b0edf162-910a-11d2-b632-00c04f79498e)15 / 0: phwnd / HWND* OutMicrosoftRegistered exact borrowed property; completes IMSVidCtl.
IMSVidRect::get_HWnd (7f5000a6-a440-47ca-8acc-c0e75531a2c2)15 / 0: HWndVal / HWND* OutMicrosoftRegistered exact borrowed property; completes IMSVidRect.
IMSVidVRGraphSegment::get_Owner (dd47de3f-9874-4f7b-8b22-7cb2688461e7)21 / 0: Window / HWND* OutNo attached Microsoft documentationOwnership/lifetime not proven; excluded.
IMFPMediaPlayer::GetVideoWindow (a714590a-58af-430a-85bf-44f5ec838d85)31 / 0: phwndVideo / HWND* OutMicrosoftRegistered exact borrowed video window; completes IMFPMediaPlayer.
IMFVideoDisplayControl::GetVideoWindow (a490b1e4-ab84-4d31-a1b2-181e03b1077a)10 / 0: phwndVideo / HWND* OutMicrosoftRegistered exact borrowed video window; another interface contract still blocks completeness, so this row adds no complete interface.
IWMPPluginUI::Create (4c5e8f9f-ad3e-4bf9-9753-fcd30d6d38dd)4 / 1: phwndWindow / HWND* InOutMicrosoftCreates a plug-in window and is InOut; lifecycle-owned/destroyable, excluded.
IPhotoAcquireOptionsDialog::Create (00f2b3ee-bf64-47ee-89f4-4dedd79643f2)4 / 1: phWndDialog / HWND* OutMicrosoftCreates a dialog with a separate lifecycle; excluded.
ISpThreadControl::StartThread (a6be4d73-4403-4358-b22d-0346e23b1764)4 / 1: phwnd / HWND* OutNo attached Microsoft documentationStarts a thread/window lifecycle paired with stop behavior; not a generic borrowed getter.
IAuthenticate::Authenticate (79eac9d0-baf9-11ce-8c82-00aa004ba90b)3 / 0: phwnd / HWND* OutNo attached Microsoft documentationParent-window ownership contract not proven from attached evidence; excluded.
IAuthenticateEx::AuthenticateEx (2ad1edaf-d83d-48b5-9adf-03dbe19f53bd)4 / 0: phwnd / HWND* OutNo attached Microsoft documentationParent-window ownership contract not proven from attached evidence; excluded.
IInternetSecurityMgrSite::GetWindow (79eac9ed-baf9-11ce-8c82-00aa004ba90b)3 / 0: phwnd / HWND* OutNo attached Microsoft documentationSite-window lifetime not proven from attached evidence; excluded.
IWindowForBindingUI::GetWindow (79eac9d5-bafa-11ce-8c82-00aa004ba90b)3 / 1: phwnd / HWND* OutNo attached Microsoft documentationBinding-UI parent lifetime not proven from attached evidence; excluded.
IActiveScriptSiteWindow::GetWindow (d10f6761-83e9-11cf-8f20-00805f2cd064)3 / 0: phwnd / HWND* OutNo attached Microsoft documentationSite-window lifetime not proven from attached evidence; excluded.
IWebApplicationHost::get_HWND (cecbd2c3-a3a5-4749-9681-20e9161c6794)3 / 0: hwnd / HWND* InOutMicrosoftInOut metadata; excluded.
IDataSourceLocator::get_hWnd (2206ccb2-19c1-11d1-89e0-00c04fd7a829)7 / 0: phwndParent / HWND* OutNo attached Microsoft documentationParent-window lifetime not proven; excluded.
IUpdateInstaller::get_ParentHwnd (7b929c68-ccdc-4226-96b1-8724600b54c2)11 / 0: retval / HWND* OutMicrosoftRegistered exact borrowed configured-parent property; completes the four-member IUpdateInstaller family.
IDesktopWindowTargetInterop::get_Hwnd (35dbf59e-e3f9-45b0-81e7-fe75f4145dc9)3 / 0: value / HWND* OutNo attached Microsoft documentationTarget-window lifetime not proven from attached evidence; excluded.
IWindowGraphicsCaptureItemInterop::GetWindow (38e4c48b-94e6-4c44-9cfa-968193316c0c)3 / 0: window / HWND* InOutNo attached Microsoft documentationInOut metadata; excluded.
IIsolatedEnvironmentInterop::GetHostHwndInterop (85713c2e-8e62-46c5-8de2-c647e1d54636)3 / 1: hostHwnd / HWND* OutNo attached Microsoft documentationHost-window lifetime not proven; excluded.
IAccPropServices::DecomposeHwndIdentityString (6e26e776-04f0-495d-80e4-3330352e3169)11 / 2: phwnd / HWND* OutMicrosoftDecoded borrowed identity, but the multi-output contract was not needed for final coverage.
IUIAutomationElement::get_CachedNativeWindowHandle (d22108aa-8ac5-49a5-837b-37bbb3d7591e)68 / 0: retVal / HWND* OutMicrosoftRegistered exact borrowed cached identity; together with the current-value row completes the nine-member IUIAutomationElement family.
IUIAutomationElement::get_CurrentNativeWindowHandle (d22108aa-8ac5-49a5-837b-37bbb3d7591e)36 / 0: retVal / HWND* OutMicrosoftRegistered exact borrowed current identity; together with the cached-value row completes the nine-member IUIAutomationElement family.
ITextHost2::TxGetWindow (IID absent)43 / 0: phwnd / HWND* InOutMicrosoftIID-less callback declaration plus InOut metadata; excluded from projection and the final denominator.
IActiveIMMApp::GetDefaultIMEWnd (08c0e040-62d1-11d1-9326-0060b067b86e)26 / 1: phDefWnd / HWND* OutNo attached Microsoft documentationLifetime not proven; excluded.
IActiveIMMIME::CreateSoftKeyboard (08c03411-f96b-11d0-a475-00aa006bcc59)73 / 4: phSoftKbdWnd / HWND* OutNo attached Microsoft documentationCreates a soft-keyboard window with a separate destroy lifecycle; excluded.
IActiveIMMIME::GetDefaultIMEWnd (08c03411-f96b-11d0-a475-00aa006bcc59)26 / 1: phDefWnd / HWND* OutNo attached Microsoft documentationLifetime not proven; excluded.
IBrowserService2::CreateViewWindow (68bd21cc-438b-11d2-a560-00a0c92dbfe8)45 / 3: phwnd / HWND* OutMicrosoftCreates a view window with explicit lifecycle; excluded.
IBrowserService2::GetViewWindow (68bd21cc-438b-11d2-a560-00a0c92dbfe8)47 / 0: phwndView / HWND* OutMicrosoftBorrowed existing view window; not needed for final coverage.
IBrowserService2::v_MayGetNextToolbarFocus (68bd21cc-438b-11d2-a560-00a0c92dbfe8)88 / 4: phwnd / HWND* OutMicrosoftBorrowed focus target, but deprecated multi-parameter semantics remain outside the registry.
IFileIsInUse::GetSwitchToHWND (64a1cbf0-3a1a-4461-9158-376969693950)6 / 0: phwnd / HWND* OutMicrosoftRegistered exact borrowed application window; another interface contract still blocks completeness, so this row adds no complete interface.
IFolderFilter::GetEnumFlags (9cc22886-dc8e-11d2-b1d0-00c04f8eeb3e)4 / 2: phwnd / HWND* OutMicrosoftBorrowed owner identity in a multi-output contract; not needed for final coverage.
IShellBrowser::GetControlWindow (000214e2-0000-0000-c000-000000000046)13 / 1: phwnd / HWND* OutMicrosoftBorrowed existing control window; not needed for final coverage.
IShellMenu::GetMenu (ee1f7637-e138-11d1-8379-00c04fd918d0)8 / 1: phwnd / optional HWND* OutMicrosoftBorrowed identity, but optional output is outside the required-output registry.
IShellView::CreateViewWindow (000214e3-0000-0000-c000-000000000046)9 / 4: phWnd / HWND* OutMicrosoftCreates a view window paired with DestroyViewWindow; excluded.
IShellView3::CreateViewWindow3 (ec39fa88-f8af-41c5-8421-38bed28f4673)20 / 8: phwndView / HWND* OutMicrosoftCreates a view window paired with view destruction; excluded.
ITextInputPanel::get_AttachedEditWindow (6b6a65a5-6af3-46c2-b6ea-56cd1f80df71)3 / 0: AttachedEditWindow / HWND* OutMicrosoftRegistered exact borrowed attached edit window; completes ITextInputPanel.
ITextStoreACP::GetWnd (28888fe3-c2a0-483a-a3ea-8cb1ce51ff3d)28 / 1: phwnd / HWND* OutMicrosoftBorrowed owner window; not needed for final coverage.
ITextStoreAnchor::GetWnd (9b2077b0-5f18-4dec-bee9-3cc722f5dfe0)26 / 1: phwnd / HWND* OutMicrosoftBorrowed owner window; not needed for final coverage.
ITfContextOwner::GetWnd (aa80e80c-2021-11d2-93e0-0060b067b86e)7 / 0: phwnd / HWND* OutMicrosoftRegistered exact borrowed owner window; completes ITfContextOwner.
ITfContextView::GetWnd (2433bf8e-0f9b-435c-ba2c-180611978c30)6 / 0: phwnd / HWND* OutMicrosoftRegistered exact borrowed view window; completes ITfContextView.
IEnumManagerFrames::Next (3caa826a-9b1f-4a79-bc81-f0430ded1648)3 / 1: ppWindows / HWND** OutNo attached Microsoft documentationAmbiguous pointer-to-pointer array/ownership contract; excluded.

The 22 registry declarations make 45 complete HWND-bearing interface projections after inheritance. The 12 additions above recover 19 complete interfaces: nine in the IUIAutomationElement family, four in the IUpdateInstaller family, and six individual interfaces. IOverlay, IMFVideoDisplayControl, and IFileIsInUse remain incomplete for unrelated contracts and therefore add no complete-interface census entries. After removing enum-name ownership inference, requiring distinct actual-length parameters to be exact Out values, and separating counted character pointers from terminated strings, the HWND-specific census was 5,567 / 7,929. The exact CoTaskMem output-ownership, parameter-direction, and null-input registries subsequently promote 114 additional complete interfaces, bringing that census to 5,681. The dedicated target-device-independent TYMED_HGLOBAL FORMATETC/STGMEDIUM model promotes 10 more interfaces after restricting InOut to proven caller-allocation-preserving contracts, bringing that census to 5,691. The variable-length WAVEFORMATEX model and three pinned audio method contracts promote five more interfaces, bringing that census to 5,696. The exact IMMDevice::Activate and GetId contracts promote one more complete interface, bringing that census to 5,697, unchanged by PR2 completion and PR3 copy-only facades. PR4's explicit overload names move 24 previously raw-metadata-complete interfaces into the safe set, bringing the current literal census to 5,721 / 7,929 = 72.152857%. The result remains above the 70% target without admitting any unmodeled target-device, storage-medium, audio-output ownership, ownership-transfer, or callback shape.

CI reproduces this number with dynwinrt-codegen com-census --json and fails if the denominator changes, complete generation drops below 5,721, or coverage falls below 70%.

Public-code frequency snapshot

There is no authoritative Microsoft ranking of COM interface usage. The table below is a reproducible demand proxy based on public GitHub code, not runtime telemetry.

The snapshot was collected on 2026-07-29 with GitHub code search:

<TOKEN> extension:cpp
  NOT path:test
  NOT path:tests
  NOT path:third_party
  NOT path:vendor
  NOT path:external
  NOT path:generated

The survey selected 30 representative desktop COM interfaces across COM infrastructure, Shell, OLE, graphics, audio, WMI, XML, and WebView2. IID_IDispatch and IID_IStream were searched instead of their bare names to reduce collisions with unrelated classes and C++ std::istream.

Two metrics are reported:

  • .cpp hits is GitHub's total matching-file count after the best-effort path exclusions above.
  • Repos / first 100 is the number of distinct repositories represented in the first 100 matching files. It prevents one large repository from being mistaken for broad adoption, but it is not a count of every matching repository.

Vendored code can still appear under other directory names, search ranking and repository contents change over time, and interfaces used through wrappers may not mention the native symbol. Treat the numbers as relative prevalence only.

Each candidate was then checked against Microsoft.Windows.SDK.Win32Metadata 69.0.7-preview Windows.Win32.winmd, and the current generator was run with --dry-run against the resolved namespace.

RankInterface/search token.cpp hitsRepos / first 100In Win32 winmdCurrent codegen
1ID3D11Device27,55287YesFail closed: untyped output ownership
2IDXGIFactory17,43283YesFail closed: untyped output ownership
3IDataObject10,64844YesGenerates completely for target-device-independent TYMED_HGLOBAL; generated fake-vtable coverage exercises GetData/GetDataHere/Query/GetCanonical/SetData
4IMalloc10,62456YesGenerates completely and is live-tested through CoGetMalloc
5IClassFactory6,71270YesGenerates completely and is live-tested through CoGetClassObject
6IDispatch via IID_IDispatch6,40846YesComplete inherited interface generates; Invoke uses dedicated DISPPARAMS/EXCEPINFO and explicit optional-output requests
7IPersistFile5,99697YesGenerates and live-tested
8IConnectionPoint5,83251YesGenerates; callback objects passed to Advise must satisfy the complete validated same-thread implementation subset
9IWbemServices5,68076YesGenerates with exact synchronous/semisynchronous conditional outputs and native-null context
10IWICImagingFactory4,53683YesGenerates and live-tested
11IDropTarget4,36857YesGenerates for client calls and dynamic JavaScript implementation; live E2E covers by-value POINTL, scalar/InOut callback ABI, libffi dispatch, and multi-interface QueryInterface
12IShellFolder4,05633YesFail closed: untyped PIDL output ownership (and later STRRET union ABI)
13IFileDialog4,04898YesGenerates; inherited methods tested through IFileOpenDialog
14IXMLDOMDocument3,78446YesFail closed: inherited unsupported Automation shapes beyond scalar VARIANT
15ID2D1Factory3,75292YesGenerates; requires flat factory acquisition and native input structs
16IDWriteFactory3,71276YesGenerates; requires flat factory acquisition
17IStream via IID_IStream3,56041YesGenerates completely; WIC live coverage exercises inherited buffers, seek, STATSTG, and Clone HRESULT propagation
18IPropertyStore3,40077YesGenerates completely and is live-tested with an unsaved ShellLink
19IShellItem3,02876YesGenerates; acquisition/live test still needed
20IMMDeviceEnumerator2,93283YesGenerates; live result depends on audio services/devices
21IBindCtx2,66042YesGenerates with exact BIND_OPTS.cbStruct = sizeof(BIND_OPTS) initialization and live CreateBindCtx coverage
22IFileOpenDialog2,53692YesGenerates and live-tested without showing UI
23IRunningObjectTable2,53250YesGenerates with POD FILETIME; acquisition/live test still needed
24IAudioClient2,50082YesGenerates completely with typed variable-length audio formats and exact IsFormatSupported/GetMixFormat ownership
25IShellLinkW2,12867YesGenerates and is live-tested with nested/fixed-array POD WIN32_FIND_DATAW
26ITaskbarList3 / TaskbarList1,67287YesInterface and newable coclass generate and are live-tested
27ICoreWebView21,60835NoDefined in WebView2 metadata, not Windows.Win32.winmd
28IFileSaveDialog1,18896YesGenerates; live test still needed
29IFileOperation76879YesGenerates and live-tested
30ITaskService46172YesFail closed: inherited unsupported Automation shapes

What the snapshot shows

  • 29 of 30 candidates are defined as IUnknown-rooted interfaces in Windows.Win32.winmd. ICoreWebView2 is the only external-metadata case.
  • 24 of 29 Win32-metadata candidates pass complete codegen validation. 5 of 29 fail closed on an unsupported ABI or ownership shape.
  • Among the top 10 by .cpp hits, only IClassFactory, IDataObject, IDispatch, IPersistFile, IConnectionPoint, IWICImagingFactory, and IMalloc pass complete codegen.
  • The largest unsupported demand clusters are:
    • discriminated resource unions and non-POD layout (parts of Shell, streams, and non-HGLOBAL data-transfer media);
    • Automation contracts beyond the exact supported IDispatch compounds (XML and Task Scheduler BYREF/InOut and nested ownership);
    • explicit output ownership (notably DXGI/private-data APIs);
    • remaining interface in/out replacement semantics; and
    • unsupported PROPVARIANT alternatives in Property System APIs beyond IPropertyStore.
  • Ten frequency-survey candidates have generated live coverage: IPersistFile, IWICImagingFactory, IFileDialog through FileOpenDialog, TaskbarList, FileOperation, IShellLinkW, IStream, IPropertyStore, IMalloc, and IClassFactory. IBindCtx also has live runtime coverage.

This means the current suite provides useful ABI breadth, but it does not cover every high-frequency interface. In particular, graphics interfaces, advanced audio, non-HGLOBAL data transfer, and live real-object IDispatch::Invoke coverage remain material gaps.

Engineering priority map

The frequency snapshot is only one input. Test priority also considers stock Windows availability, deterministic behavior, whether an API requires UI or hardware, and whether it adds a distinct ABI shape.

InterfaceTypical useCurrent status
ISequentialStream / IStreamOLE streams, imaging, shell, serializationComplete generation includes documented Read/Write byte contracts and owned STATSTG; WIC live coverage exercises buffers, seek, stat, and Clone HRESULT propagation.
IStorageStructured storageComplete generation reuses owned STATSTG, nullable input buffers, and exact reserved-null pointer contracts.
IOpcSignatureCustomObjectOPC signature custom XMLGetXml generates as a CoTaskMem-owned callee byte buffer; acquisition is application-specific.
IDiscRecorderLegacy IMAPI recorderComplete generation now includes the exact GetRecorderGUID two-call method and documentation-correct getDisplayNames(): [string, string, string] BSTR outputs.
IMallocCOM task allocatorComplete generation is gated by exact IID/slot/shape evidence. Opaque values reject forged/stale addresses; destructive and size operations enforce allocator identity, while DidAlloc permits borrowed cross-allocator inspection.
IPersistFileLoading and saving persistent COM objectsCore tests query it from IShellLinkW; Node activates the Shell Link coclass directly as IPersistFile and verifies GetClassID.
IShellLinkWShortcut creation and inspectionCore runtime tests cover strings, u16, enums, and scalar outputs; generated Node coverage round-trips GetPath with nested FILETIME and fixed WCHAR-array WIN32_FIND_DATAW POD storage.
IThumbnailProviderShell thumbnailsA generated-wrapper integration test receives a real HBITMAP from a fake COM vtable, transfers it into DynComOwnedHandle, and verifies explicit DeleteObject release plus best-effort Drop cleanup.
FileOpenDialogDesktop file selectionNode test covers coclass construction and option round-trip without showing UI.
FileOperationShell copy/move/delete operationsNode test covers coclass construction, unsigned flags, and state without modifying files.
IWICImagingFactoryWindows Imaging ComponentNode test activates WIC and creates an interface-valued stream.
TaskbarList / ITaskbarList3Taskbar progress and window stateNode test covers new, inherited vtable slots, HWND values, BOOL, enums, u64, and as/tryAs/supports.
IDataTransferManagerInteropHWND-to-WinRT data-transfer bridgeCore and Node tests cover IUnknown-rooted interop and interface output.
ISystemMediaTransportControlsInteropHWND-to-WinRT media controlsNode test covers IInspectable-rooted interop and use of the returned WinRT object.
IClassFactoryLow-level COM activationComplete generation and public CoGetClassObject acquisition are live-tested with paired server locking and owned CreateInstance output.
IBindCtx / IRunningObjectTableMonikers and object bindingBoth generate. BIND_OPTS carries an exact size initializer, and explicit bytes with a zero or incorrect cbStruct fail before native dispatch.
ICreateErrorInfo / IErrorInfoCOM rich error informationComplete generation and acquisition are live-tested for GUID, wide strings, owned BSTR output, thread-local storage, and one-shot consumption.
IMMDeviceEnumerator / IMMDeviceAudio endpoint discovery and activationComplete safe generation includes typed null-parameter activation for the documented IID subset and CoTaskMem-owned device IDs projected as strings. projectAs wraps borrowed managed device values with an independent QI owner; live behavior depends on audio services and endpoints.
IWbemServicesWMI queries and method invocationComplete generation uses exact sync/semisync output selection; selected outputs are owned COM references and pCtx is native null in the closed safe overloads.
IAudioClient / IAudioClient2 / IAudioClient3Low-level audio streamingComplete safe generation uses DynComAudioFormat; exact fingerprinted contracts model shared/exclusive closest-format selection and CoTaskMem-owned format outputs. Generated fake-vtable coverage is hardware-independent.
IDispatchAutomation and scriptingComplete inherited real-metadata generation passes. GetIDsOfNames projects as string[] -> number[]; Invoke accepts DynComDispatchParams and explicit result/excepInfo/argErr request options, returning dedicated owning wrappers. Derived Automation interfaces remain independently validated.
IPropertyStoreShell/property metadataComplete generation and live ShellLink SetValue/GetValue/Commit coverage pass with dedicated PROPVARIANT ownership.
IDataObjectClipboard and drag-and-dropComplete safe generation for target-device-independent TYMED_HGLOBAL; generated fake-vtable coverage verifies owned GetData output, caller-owned GetDataHere storage, semantic HRESULT, canonical FORMATETC, and borrowed SetData input.

Automated coverage

Classic COM interfaces are exercised across core and generated Node coverage. Core live tests are in crates/dynwinrt/src/com.rs. The seventeen Node runners are in tests/e2e/runners/com and are generated and executed by tests/e2e/e2e_test.ps1.

Automation coverage remains scoped to proven contracts rather than claimed as general interface support. Local fake COM vtables copy pointer-shaped VARIANT, SAFEARRAY, and PROPVARIANT inputs to owned outputs and verify conversion, failure cleanup, typed-array rejection, interface AddRef/Release balance, and native-union active-field validation. SAFEARRAY tests additionally cover signed multidimensional bounds, typed scalar/bool/BSTR/VARIANT values, exact interface IID creation/inspection/mismatch cleanup, generic-descriptor per-element validation, nullable output, and descriptor VARTYPE/width/rank validation. Dedicated BSTR fake-vtable tests pass embedded-NUL input and output values and exercise unchanged, replaced, and nulled InOut slots on both successful and failed HRESULT paths with exact allocation/free counters. Separate x64 and i686 fake-vtable tests pass scalar, BSTR, and interface VARIANT values by value and verify windows-rs layout, libffi aggregate classification, deep-copy isolation, HRESULT failure cleanup, panic-safe drop, wrong argument shape, unsupported tags, BYREF rejection, and output/InOut signature rejection. Real Windows.Win32 metadata regressions inspect ITargetNotify2, inherited IExecAction2, IDiscRecorder, IPhotoAcquireDeviceSelectionDialog, BSTR arrays/double pointers/custom cleanup, IAccessible::accSelect, IUIAutomation::CreatePropertyCondition, IPropertyBag VARIANT pointer/InOut, exact SAFEARRAY families across UI Automation, WMI, FSRM, PLA, Remote Desktop, Camera UI, tuner, and Mobile Broadband APIs, IPropertyStore PROPVARIANT output, and ITypeComp BINDPTR union facts. Complete interfaces still stop at their next unrelated or unproven contract.

The exact IDispatch::Invoke contract uses a COM-local captured-HRESULT plan. Successful calls expose only an optional result VARIANT. Failed calls discard and clear pVarResult, finalize requested EXCEPINFO storage once, preserve a meaningful argErr, and generate an Error with hresult plus optional excepInfo, argErr, and deferred-fill cause fields.

InterfaceTest layerRepresentative coverage
IShellLinkWCore + Node E2EGenerated activation through its IID, wide strings, hotkeys/show command, and GetPath with zeroed 592-byte WIN32_FIND_DATAW POD storage.
IPersistFileCore + Node E2EQueryInterface/direct IID activation, owned returned reference, GUID output, and deterministic release.
IMallocCore + Node E2EExact opaque allocation projection, allocator identity for ownership-sensitive operations, borrowed DidAlloc inspection, automatic/explicit cleanup, resize, and direct scalar/void returns.
IClassFactoryCore + Node E2EPublic CoGetClassObject, owned factory reference, paired LockServer, dynamic-IID CreateInstance, and +1 output adoption.
ICreateErrorInfo / IErrorInfoCore + Node E2EPublic acquisition, GUID/PWSTR setters, owned BSTR getters, thread-local isolation, and consume-on-read behavior.
IStreamCore + Node E2ETyped counted byte input/output buffers, actual u32 lengths, i64 seek, owned STATSTG, IStream** clone ABI, and stock-WIC Clone HRESULT propagation.
IPropertyStoreNode E2EGenerated PROPERTYKEY POD and owned PROPVARIANT values against an unsaved ShellLink, including Commit.
IBindCtxCore + NodeExact multi-architecture BIND_OPTS layout, automatic cbStruct, pre-dispatch validation, and live CreateBindCtx round trip.
TaskbarList / ITaskbarList3Node E2ECoclass construction, inherited slots, runtime QI views, HWND, BOOL, enum, and u64.
FileOperationNode E2ECoclass construction, unsigned flags, and state query.
FileOpenDialog / IFileDialogEventsCore + Node E2ESTA coclass construction, generated synchronous JS implementation, self-vtable callback dispatch, public native-value bridge, and real Advise/Unadvise without showing UI.
IDropTargetCore + Node E2EDynamic libffi callbacks, interface/scalar/POD/InOut parameters, generated multi-interface composition, and QueryInterface to the additional view.
IWICImagingFactoryNode E2EExplicit CLSID activation and typed interface output.
IDataTransferManagerInteropCore + Node E2EIUnknown base, HWND, REFIID, and WinRT interface output.
ISystemMediaTransportControlsInteropNode E2EIInspectable base and meaningful use of the returned WinRT projection.

Additional regression tests cover:

  • a test-only windows-rs ABI oracle for selected stable interfaces and native layouts: interface IIDs, host-target size/alignment, and field offsets for RECT, THUMBBUTTON, WIN32_FIND_DATAW, DISPPARAMS, EXCEPINFO, and the complete IFileDialogEvents callback vtable; windows-rs is not a production dispatch backend and does not replace semantic validation;
  • rejection of duplicate ownership through exported pointer bits;
  • generated COM sink Worker teardown, wrong-thread late invocation, JavaScript exception-to-HRESULT behavior, and callback-resource cleanup;
  • detached TypedArray backing storage;
  • BSTR exact-length allocation, replacement, null/failure cleanup, and CoTaskMem cleanup;
  • exact SAFEARRAY registry identity/raw-shape drift, VARTYPE constants, windows-rs UI Automation IIDs, signed bounds, typed-interface descriptor and per-element QI validation, BSTR/scalar/interface cleanup, nullable output, and nearest unsupported input-SAFEARRAY**, InOut, and record-array shapes;
  • x86 pointer width;
  • x86/x64/ARM64 POD layout computation, nested structs, fixed arrays, and all value/pointer/out/in-out storage shapes;
  • typed buffer zero initialization, actual-length slicing, bounded sizing, CoTaskMem transfer, failure suppression, detached backing, width, length, and alignment validation;
  • exact fixed-capacity IMFAttributes::GetBlob metadata, natural GUID/capacity rendering, runtime-owned zeroed storage, successful actual-length slicing, capacity overflow, short-buffer failure, and ordinary HRESULT failure;
  • exact cited rejection of all seven declaring GetPrivateData families whose payload may carry an AddRef'd interface, including mutation tests for IID, method/slot, REFGUID depth/constness, count direction/type, buffer direction/depth/optionality, actual count, and HRESULT return;
  • deterministic fake-vtable IEnum*::Next partial-success, optional-fetched, fetched-overflow, interface transfer, unused-capacity, and failed-HRESULT cleanup, plus complete real-metadata generation of IEnumGUID and IEnumConnectionPoints (including the IConnectionPoint dependency);
  • borrowed UTF-16/ANSI string-pointer arrays, stable pointer tables, shared count agreement, aligned zeroed scalar output arrays, and complete real IDispatch generation;
  • fake-vtable DISPPARAMS reversal/named-ID/contiguous-storage checks and EXCEPINFO immediate/deferred success, callback failure, HRESULT failure, optional-null output, and conversion-failure cleanup;
  • unsupported unions, bitfields, flexible arrays, unknown layouts, and nested owned fields;
  • required parameter preservation;
  • unsigned enum values;
  • mixed, COM-only, and order-independent incremental package generation; and
  • separation of @microsoft/dynwinrt from @microsoft/dynwinrt/com.

Run the live Classic COM suite with:

$env:DYNWINRT_WIN32_WINMD = "C:\path\to\Windows.Win32.winmd"
.\tests\e2e\e2e_test.ps1 -SkipBuild -Lang com

Reference counting and ownership

COM interface references and Win32 handles must not be treated the same.

Value sourceOwnership in dynwinrtCleanup
CoCreateInstance resultOwned +1 COM referenceAutomatic Release on DynWinRtValue drop/GC, or explicit release().
QueryInterface / cast() / projectAs() resultOwned +1 COM referenceAutomatic Release, independently of the borrowed source value or wrapper.
Generated IMMDevice.activate() resultOwned +1 COM reference for the registered allowed targetAutomatic Release; the intermediate activation owner is released after projection, including on projection failure.
Typed interface out parameterOwned +1 COM reference from the calleeAutomatic Release.
Interface passed as [in]Borrowed for the duration of the callNo ownership transfer unless the callee explicitly retains it with AddRef.
Numeric raw pointerBorrowedNever automatically released or freed.
Buffer/TypedArray pointerBorrowed and owner-backedBacking storage is retained and revalidated; it cannot be adopted as a COM owner.
Caller-owned counted outputBorrowed during the call; returned bytes are an owned exact-range copyNative storage is zeroed first. Failed HRESULTs return no copied result.
IEnum*::Next interface elementEach non-null initialized slot is an owned +1 referenceFetched slots move once into managed values; slots are nulled on transfer. Failure, overflow, conversion error, and unused initialized capacity release remaining slots within the requested bound.
Callee-allocated counted CoTaskMem outputAllocation transfers once to the COM buffer planExact bytes are copied and the original allocation is freed once with CoTaskMemFree.
adoptComPointer() inputMust be a native output carrying an existing +1 referenceOwnership transfers to the returned wrapper.
Callee-allocated CoTaskMem stringOwned allocationGenerated conversion frees it with CoTaskMemFree.
By-value BSTR inputCaller-owned call-local allocation; borrowed by the calleeRuntime frees it after the call.
Scalar BSTR outputOwned allocationGenerated conversion frees it with SysFreeString.
Scalar BSTR InOutCaller owns the initial and final slot; callee may free/replace the initial allocationRuntime owns one unique call-local slot, transfers the successful final value once, and frees the valid final value on failure.
SAFEARRAY inputCaller-owned descriptor and elements, borrowed for the invocationA per-array lock keeps the descriptor stable; no ownership transfer occurs.
SAFEARRAY outputCallee-owned descriptor transferred through SAFEARRAY**Runtime adopts exactly once after HRESULT success and exact descriptor validation; RAII calls SafeArrayDestroy on rejection, conversion failure, or final drop.
Typed interface SAFEARRAY elementSafeArray-owned interface reference with an exact proven IIDInput creation and output adoption QueryInterface-validate each non-null value; descriptors with the exact IID, IID_IUnknown, or no IID are distinguished, and the latter two rely on element proof for the identity fallback. SafeArray APIs perform element AddRef/Release and SafeArrayDestroy releases remaining elements.
HANDLE, HWND, HBITMAP, etc.Win32 resource value, not a COM referenceUse the resource-specific API such as CloseHandle, DestroyWindow, or DeleteObject when required.

The JavaScript ownership provenance checks intentionally prevent turning a borrowed numeric or TypedArray pointer into a second owner. This avoids two wrappers releasing the same COM reference.

Every generated Classic COM class also exposes a public release() method delegating to the wrapped DynWinRTValue's own release(). Calling release() more than once is safe (the underlying value is cleared to null the first time).

Test selection guidance

Prefer new CI tests that:

  1. use stock Windows components;
  2. require no network, optional software, or user input;
  3. avoid persistent filesystem or system-state changes;
  4. assert meaningful results rather than activation alone;
  5. add a distinct ABI or ownership shape; and
  6. clean up every COM reference, native allocation, and Win32 resource.

Interfaces that require Office, deprecated Internet Explorer automation, active drag-and-drop, a populated clipboard, audio hardware, or an Explorer desktop should remain optional or local-only tests.