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
In the trimmable typemap (NativeAOT), Java interface peer types (e.g. listener Implementors) declare their n_* native callback as a private static method on the interface type. For interfaces that live in a binding/AndroidX assembly, ILC trims that callback (nothing references it within its own assembly in the trimmable path), so the generated proxy forwarder "will always throw":
This means interface listener callbacks from binding/AndroidX libraries don't work under the trimmable typemap.
What we tried (and reverted)
PR #11769 originally switched interface-implementation marshal methods to direct managed dispatch (ShouldCallManagedMethodDirectly returned true for isInterfaceImplementation), so the generated UCO would do GetObject<TInterface>(handle).Method(...) itself instead of forwarding to the (trimmed) static n_*.
This caused a runtime regression: infinite recursion / stack overflow for all interface listeners, including built-in Mono.Android ones (ViewTreeObserver.GlobalLayout, etc.). Reproduced on an arm64 emulator with a minimal app:
→ Fatal signal 11 (SIGSEGV) ... Cause: stack pointer is not in a rw map; likely due to stack overflow. The callback log never appears. Disabling the direct-dispatch change makes the handler fire correctly.
The direct-dispatch UCO resolved the peer as the Invoker (GetObject(handle, typeof(TInterface)) creates the invoker when the user's Implementor peer isn't returned), and the invoker forwards the call back to Java → native → UCO → invoker → … until the stack overflows. So direct dispatch is strictly worse than the original static-n_* forwarding, which dispatches correctly for Mono.Android interfaces.
The direct-dispatch change has been reverted (the non-generic JavaPeerProxy base fix for interface proxies in #11769 is kept — that one is correct and fixes a separate TypeLoadException). This unblocks GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunch and avoids breaking every interface listener.
What still needs solving (this issue)
Binding/AndroidX interface listener callbacks still don't work under the trimmable typemap because the interface's private static n_* callback is trimmed → "will always throw". We need a correct fix that does not reintroduce the recursion, e.g.:
Preserve / root the interface n_* callbacks so ILC keeps them (the forwarder then resolves), rather than bypassing them with direct dispatch; or
A direct-dispatch variant that provably resolves the user Implementor peer (never the Invoker) — i.e. understand why GetObject returns the invoker here and ensure the UCO dispatches to the already-registered managed peer; or
Generate the JCW/registration so the binding's n_* is reachable in the trimmable assembly.
Acceptance criteria
Binding/AndroidX interface listener callbacks fire at runtime under NativeAOT (e.g. IOnBackStackChangedListener), with a device test.
No regression for built-in Mono.Android interface listeners (GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunch stays green).
No "will always throw" ILC diagnostics for the generated interface proxies.
Summary
In the trimmable typemap (NativeAOT), Java interface peer types (e.g. listener
Implementors) declare theirn_*native callback as aprivate staticmethod on the interface type. For interfaces that live in a binding/AndroidX assembly, ILC trims that callback (nothing references it within its own assembly in the trimmable path), so the generated proxy forwarder "will always throw":This means interface listener callbacks from binding/AndroidX libraries don't work under the trimmable typemap.
What we tried (and reverted)
PR #11769 originally switched interface-implementation marshal methods to direct managed dispatch (
ShouldCallManagedMethodDirectlyreturnedtrueforisInterfaceImplementation), so the generated UCO would doGetObject<TInterface>(handle).Method(...)itself instead of forwarding to the (trimmed) staticn_*.This caused a runtime regression: infinite recursion / stack overflow for all interface listeners, including built-in Mono.Android ones (
ViewTreeObserver.GlobalLayout, etc.). Reproduced on an arm64 emulator with a minimal app:→
Fatal signal 11 (SIGSEGV) ... Cause: stack pointer is not in a rw map; likely due to stack overflow.The callback log never appears. Disabling the direct-dispatch change makes the handler fire correctly.The direct-dispatch UCO resolved the peer as the Invoker (
GetObject(handle, typeof(TInterface))creates the invoker when the user'sImplementorpeer isn't returned), and the invoker forwards the call back to Java → native → UCO → invoker → … until the stack overflows. So direct dispatch is strictly worse than the original static-n_*forwarding, which dispatches correctly for Mono.Android interfaces.The direct-dispatch change has been reverted (the non-generic
JavaPeerProxybase fix for interface proxies in #11769 is kept — that one is correct and fixes a separateTypeLoadException). This unblocksGlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunchand avoids breaking every interface listener.What still needs solving (this issue)
Binding/AndroidX interface listener callbacks still don't work under the trimmable typemap because the interface's
private static n_*callback is trimmed → "will always throw". We need a correct fix that does not reintroduce the recursion, e.g.:n_*callbacks so ILC keeps them (the forwarder then resolves), rather than bypassing them with direct dispatch; orGetObjectreturns the invoker here and ensure the UCO dispatches to the already-registered managed peer; orn_*is reachable in the trimmable assembly.Acceptance criteria
IOnBackStackChangedListener), with a device test.GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunchstays green).References
GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunch(NativeAOT),tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs.src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs(ShouldCallManagedMethodDirectly),Generator/ExportMethodDispatchEmitter.cs.