Skip to content

feat(runtime): primitive arrays, interop.Array.create, delegates boxed for Object params - #41

Merged
triniwiz merged 1 commit into
mainfrom
feat/winrt-arrays-delegate-boxing
Nov 6, 2026
Merged

triniwiz merged 1 commit into
mainfrom
feat/winrt-arrays-delegate-boxing

Conversation

@triniwiz

@triniwiz triniwiz commented Nov 6, 2026

Copy link
Copy Markdown
Member

Arrays

Every primitive WinRT array parameter now marshals, on both engines. That covers Int32[], UInt32[], Int16[], UInt16[], Int64[], UInt64[], Single[], Double[], Boolean[], Char16[] and the byte types, and it covers both arrays the callee reads and arrays it fills. Before, only byte[] worked, and a plain JS array for byte[] failed with Unspecified error (HRESULT 0x80004005) (e.g. FileIO.WriteBytesAsync(file, [1, 2, 3])).

  • The call passes (count, pointer). The count is in elements, not bytes.

  • A typed array of the element's kind, an ArrayBuffer or a DataView is passed in place: the callee reads or writes JS memory directly. A typed array of the wrong kind is refused (Float64Array for Int32[]).

  • A plain JS array is copied, converting each element the way the matching typed array would.

  • A plain array is refused for a fill array, since the callee's writes would be lost. ParameterDeclaration::is_fill_array() reads the [out] param attribute: is_out() only means ByRef.

  • interop.Array.create(type, count) returns the matching zero-filled typed array, so an array can be allocated up front:

    const bytes = interop.Array.create('UInt8', reader.UnconsumedBufferLength);
    reader.ReadBytes(bytes);
    const ints = interop.Array.create('Int32', 4); // Int32Array

    Typings added in typings/nswinrt-helpers.d.ts.

Still unsupported: received arrays (out T[], e.g. IPropertyValue.GetInt32Array), array return values, and arrays of strings or objects.

Delegates passed as Object

UIElement.AddHandler(routedEvent, handler, handledEventsToo) takes its handler as Object and unboxes it with QueryInterface(IReference<TDelegate>). We passed the bare delegate, so it failed with E_INVALIDARG, and core falls back to instance events, which never see events a control has already handled.

JS delegates (classic JsDelegate and napi NapiDelegate) now embed a small tear-off (delegate_box.rs) that answers IInspectable and IReference<D>. Its get_Value returns the delegate itself, and its IUnknown methods forward to the delegate, so COM identity is preserved. AddHandler and RemoveHandler work.

The IID comes from GenericInstanceIdBuilder::ireference_of_delegate. A test checks it against RoGetParameterizedTypeInstanceIID. Note that windows-rs signs delegates as a bare {guid} rather than delegate({guid}), so IReference::<SomeDelegate>::IID from the windows crate doesn't match what WinRT uses.

Argument errors

When an argument can't be marshaled, the call now reports why instead of a bare E_FAIL (thread-local error::set_argument_error, picked up by format_hresult_message; napi throws a TypeError). For example: Expected an Int32Array, an ArrayBuffer, a plain array or null for parameter type Int32[]. Two interop tests asserted that such errors contain HRESULT; they now also accept these messages.

Testing

  • windows-napi npm test: 442/442.
  • New Rust tests: IReference<D> IID against the OS; the delegate box (QI, get_Value identity, refcounts); array element conversion, element counts and view checks.
  • End-to-end on Node with windows-napi, 26/26:
    • Array.create for each type.
    • ReadBytes filling a pre-allocated array, and refusing a plain array.
    • PropertyValue.Create*Array for every element type.
    • WriteBytesAsync with a plain array.
    • TextBox.AddHandler(KeyDownEvent, h, true) and RemoveHandler in a live XamlWindow; on main this throws 0x80070057.
    • The same synchronous checks pass on classic V8 (23/23).
  • cargo test -p runtime --lib: same failures as main (environment and network dependent), plus enum_type_arguments, whose 2 s wait times out under parallel load on some runs and passes alone.

Arrays: every primitive WinRT array parameter (Int32[], Double[],
Char16[], Boolean[], ...) now marshals, both arrays the callee reads and
arrays it fills; only byte[] did before. A typed array of the element's
kind, an ArrayBuffer or DataView is passed in place; a plain JS array is
copied, and refused for a fill array since the callee's writes would be
lost. interop.Array.create(type, count) allocates the matching typed
array up front, e.g. reader.ReadBytes(interop.Array.create('UInt8', n)).

Delegates: a JS delegate passed where WinRT expects an Object now also
answers IInspectable and IReference<D>, the way a projected delegate is
boxed. UIElement.AddHandler unboxes its handler that way and failed with
E_INVALIDARG; AddHandler(..., handledEventsToo) and RemoveHandler work
on both engines.

Errors: an argument that can't be marshaled reports what was expected
("Expected an Int32Array, an ArrayBuffer, a plain array or null for
parameter type Int32[]") instead of "Unspecified error (HRESULT
0x80004005)".
@coderabbitai

coderabbitai Bot commented Nov 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c956330b-475d-4f14-9b40-a559206bc323

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@triniwiz
triniwiz merged commit d7ae3d3 into main Nov 6, 2026
3 checks passed
@triniwiz
triniwiz deleted the feat/winrt-arrays-delegate-boxing branch November 6, 2026 07:20
triniwiz added a commit to NativeScript/NativeScript that referenced this pull request Nov 6, 2026
The modal Popup's Closed handler was attached with
AddHandler(Popup.ClosedEvent, ...), but Popup.Closed is a plain event,
not a routed one: Popup.ClosedEvent is undefined, the call threw and
the empty catch hid it. Clicking outside a cancelable modal closed the
Popup without running the modal's close logic, so the view kept its
modal state and the close callback never fired. The handler is now
assigned to popup.Closed and cleared with null on close.

Also corrects the TextField comment: AddHandler(KeyDownEvent, handler,
true) works with a runtime that boxes JS delegates for Object
parameters (NativeScript/windows#41); the KeyDown event stays as the
fallback for runtimes that can't.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant