You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Windows: dotnet.invoke cannot marshal projected WinRT objects, and awaits Task-returning methods on the caller thread #11616
Two verified limitations in the pinned Windows runtime's .NET bridge, blocking any JS-side use of APIs that need managed interop — most concretely, WinRT pickers that require IInitializeWithWindow owner HWND setup.
Real-world impact: Octane Xplat's files.pick/pickMultiple/export on Windows needed a fully managed workaround (app-compiled helper + DispatcherQueue enqueue) because neither projected WinRT objects nor async picker calls can cross the bridge. Verified on packaged MSIX 1.0.0.224–227, runtime @nativescript/windows@0.1.0-alpha.144.
1. Projected WinRT objects marshal as System.String, never RCWs
Every candidate carrier handed to dotnet.invoke arrives in managed code as System.String:
the picker proxy itself → System.String
picker.handle, interop.handleOf(picker).handle → Cannot convert object to primitive value
ServiceInterop.Describe (a managed helper taking object) reports System.String|IsInitWithWindow=False, so neither a ComImport IInitializeWithWindow cast nor WinRT.Interop.InitializeWithWindow can run — every call fails InvalidCastException: Failed to create a CCW for object of type 'System.String'. The bridge's CoerceBin has a WinRtRef path (tag 0x0A → Marshal.GetObjectForIUnknown) but no JS value appears to serialize to it.
2. Task-returning methods are awaited on the caller thread
dotnet.invoke on a Task<T>-returning method blocks the JS/UI thread until the task completes (AwaitIfTask in Bridge.BinaryDispatch.cs). For a modal picker this is a guaranteed deadlock: the dialog needs the same thread's message pump. Verified deterministically — a Task<string> PickSingle helper hung the packaged app on two consecutive runs; enqueueing the same work on the captured DispatcherQueue and polling a string state works.
Workaround shipped: an app-compiled ServiceInterop helper does construct + InitializeWithWindow + await in-process on the dispatcher; JS only passes long/string and polls PickState. Packaged evidence: a real FileOpenPicker/FileSavePicker dialog opened, accepted a typed-path selection, and committed a save — while every JS-side picker invoke still rejects WinRT async operation failed.
Projected objects arriving as System.String: confirmed and fixed. WinRT wrappers never exposed __native_ptr, so they fell through to toString(). The next runtime release passes WinRT objects as WinRT refs, and the bridge projects them through C#/WinRT when it's loaded. A FileOpenPicker now reaches managed code as a FileOpenPicker, and WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd) works on it.
The runtime also already calls IInitializeWithWindow.Initialize when you construct a picker, but it used the console window, which a packaged app doesn't have. It now uses the app's top-level window, so new Windows.Storage.Pickers.FileOpenPicker() from JS should no longer need managed owner setup.
Task methods blocking the caller: this was fixed after the version you're on (0.1.0-alpha.144, 2026-09-28). Task/ValueTask results are no longer awaited on the calling thread: dotnet.invoke returns right away with an awaitable result, so await it (or use NSWinRT.toPromise(result)) instead of polling. Please move to the beta dist-tag (@nativescript/windows@1.0.0-beta.18 or later).
try updating core to the pkg.pr.new298c76d and the runtime to @nativescript/windows@1.0.0-beta.19
Activity
triniwiz commentedon Nov 6, 2026
System.String: confirmed and fixed. WinRT wrappers never exposed__native_ptr, so they fell through totoString(). The next runtime release passes WinRT objects as WinRT refs, and the bridge projects them through C#/WinRT when it's loaded. AFileOpenPickernow reaches managed code as aFileOpenPicker, andWinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd)works on it.The runtime also already calls
IInitializeWithWindow.Initializewhen you construct a picker, but it used the console window, which a packaged app doesn't have. It now uses the app's top-level window, so newWindows.Storage.Pickers.FileOpenPicker()from JS should no longer need managed owner setup.Taskmethods blocking the caller: this was fixed after the version you're on (0.1.0-alpha.144, 2026-09-28).Task/ValueTaskresults are no longer awaited on the calling thread:dotnet.invokereturns right away with anawaitableresult, so await it (or useNSWinRT.toPromise(result)) instead of polling. Please move to the beta dist-tag (@nativescript/windows@1.0.0-beta.18or later).try updating core to the
pkg.pr.new298c76dand the runtime to@nativescript/windows@1.0.0-beta.19