[Windows] Menu item clicks are emitted after Menu::Open() returns, which aborts Dart FFI bindings
What happens
On the native Windows backend, Menu::Open() calls TrackPopupMenu without TPM_RETURNCMD
(src/platform/windows/menu_windows.cpp, ~line 857). Windows therefore posts WM_COMMAND for the
chosen item, Open() returns first, and the click is emitted later, when the host's message loop
dispatches that message to the hidden host window.
Any binding whose callbacks are only valid while the caller is inside the call cannot receive that late
event. With nativeapi-flutter 0.2.4 on Flutter 3.41.2 (Windows 11 26200, release build, merged
platform/UI thread as the example README asks for), every menu item click kills the process:
MenuItem: Item clicked, ID = 50331649
../../../flutter/third_party/dart/runtime/vm/runtime_entry.cc: 5034: error: Cannot invoke native callback outside an isolate.
Exception 0xc0000409 (fast-fail 7) in ucrtbase.dll. The Dart listener is created with
NativeCallable.isolateLocal (packages/nativeapi/lib/src/menu.dart), which the Dart VM refuses, and
aborts, when no isolate is on the thread.
MenuOpenedEvent and MenuClosedEvent are fine, because WM_INITMENUPOPUP / WM_UNINITMENUPOPUP arrive
while Open() is still on the stack. Only the click is late.
Reproduce
A minimal Flutter Windows app (window_manager + desktop_multi_window, nothing unusual): build a Menu,
menu.open(PositioningStrategy.cursorPosition()!, Placement.bottomStart), click any item. The process
aborts. It happens whether the menu is rebuilt per open or built once and reused (as
examples/menu_example does), and whether the item is picked with the mouse or with Down+Enter.
Suggested fix
Pass TPM_RETURNCMD, then look up the returned command id among the menu's items (recursing into
submenus) and fire that item's click before Open() returns — which is what the WinUI 3 backend
already does in menu_winui3_windows.cpp (its Click handler runs inside the session's own message
loop). Two details worth keeping: with TPM_RETURNCMD a return of 0 means "dismissed" or "failed", so
SetLastError(ERROR_SUCCESS) before the call keeps a genuine failure distinguishable; and do not add
TPM_NONOTIFY, which also suppresses WM_INITMENUPOPUP/WM_UNINITMENUPOPUP and silently drops the
opened/closed events (we tried it).
Verification we ran locally
With that change vendored into cnativeapi 0.2.4: 6 open→pick and 6 open→Escape rounds across reused and
rebuilt menus, no aborts, and each round emits MenuOpened, MenuClosed and MenuItemClicked before open()
returns. We also added a check to tests/menu_backend_test.cpp (native backend, a timer sends Down+Enter,
assert the listener ran before Open() returned): it fails on main with "Native click was not emitted
before Open() returned" and passes with the change.
Happy to send the patch and the test as a PR if that's useful — just say the word.
[Windows] Menu item clicks are emitted after
Menu::Open()returns, which aborts Dart FFI bindingsWhat happens
On the native Windows backend,
Menu::Open()callsTrackPopupMenuwithoutTPM_RETURNCMD(
src/platform/windows/menu_windows.cpp, ~line 857). Windows therefore postsWM_COMMANDfor thechosen item,
Open()returns first, and the click is emitted later, when the host's message loopdispatches that message to the hidden host window.
Any binding whose callbacks are only valid while the caller is inside the call cannot receive that late
event. With nativeapi-flutter 0.2.4 on Flutter 3.41.2 (Windows 11 26200, release build, merged
platform/UI thread as the example README asks for), every menu item click kills the process:
Exception
0xc0000409(fast-fail 7) inucrtbase.dll. The Dart listener is created withNativeCallable.isolateLocal(packages/nativeapi/lib/src/menu.dart), which the Dart VM refuses, andaborts, when no isolate is on the thread.
MenuOpenedEventandMenuClosedEventare fine, becauseWM_INITMENUPOPUP/WM_UNINITMENUPOPUParrivewhile
Open()is still on the stack. Only the click is late.Reproduce
A minimal Flutter Windows app (window_manager + desktop_multi_window, nothing unusual): build a
Menu,menu.open(PositioningStrategy.cursorPosition()!, Placement.bottomStart), click any item. The processaborts. It happens whether the menu is rebuilt per open or built once and reused (as
examples/menu_exampledoes), and whether the item is picked with the mouse or with Down+Enter.Suggested fix
Pass
TPM_RETURNCMD, then look up the returned command id among the menu's items (recursing intosubmenus) and fire that item's click before
Open()returns — which is what the WinUI 3 backendalready does in
menu_winui3_windows.cpp(itsClickhandler runs inside the session's own messageloop). Two details worth keeping: with
TPM_RETURNCMDa return of 0 means "dismissed" or "failed", soSetLastError(ERROR_SUCCESS)before the call keeps a genuine failure distinguishable; and do not addTPM_NONOTIFY, which also suppressesWM_INITMENUPOPUP/WM_UNINITMENUPOPUPand silently drops theopened/closed events (we tried it).
Verification we ran locally
With that change vendored into cnativeapi 0.2.4: 6 open→pick and 6 open→Escape rounds across reused and
rebuilt menus, no aborts, and each round emits MenuOpened, MenuClosed and MenuItemClicked before
open()returns. We also added a check to
tests/menu_backend_test.cpp(native backend, a timer sends Down+Enter,assert the listener ran before
Open()returned): it fails onmainwith "Native click was not emittedbefore Open() returned" and passes with the change.
Happy to send the patch and the test as a PR if that's useful — just say the word.