Skip to content

Conversation

@jonpryor
Copy link
Contributor

@jonpryor jonpryor commented May 9, 2025

Fixes: #10126

Context: 4b158ce
Context: dotnet/java-interop@356485e
Context: 32cff43

NativeAOT currently requires new binding assemblies, bindings which do not use JNINativeWrapper.CreateDelegate(), as one of the code paths within JNINativeWrapper.CreateDelegate() uses System.Reflection.Emit, which does not exist in NativeAOT. dotnet/java-interop@356485ee contains the generator changes needed to avoid JNINativeWrapper.CreateDelegate().

However, there is one situation in which that "requirement" is (kinda) conditional: commit 32cff43 added "builtin" JNI wrapper methods, avoiding the need for System.Reflection.Emit when overriding Java methods that match a builtin signature.

This means existing binding assemblies can work, right?

Kinda, yeah! So long as you only override methods which match a builtin signature. Which was enough to get MAUI going atop NativeAOT without rebuilding all of dotnet/android-libraries…

However, for not-entirely-understood reasons, sometimes using a builtin JNI wrapper method would cause the app to crash:

digest=============com.avalonia.safeareademo /apex/com.android.runtime/lib/bionic/libc.so (abort+) ()
XXlib/arm/libSafeAreaDemo.Android.so (SystemNative_Abort+) ()
XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_Interop_Sys__Abort+) ()
XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_RuntimeExceptionHelpers__FailFast+) ()
XXlib/arm/libSafeAreaDemo.Android.so (RuntimeFailFast+) ()
XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_EH__UnhandledExceptionFailFastViaClasslib+) ()
XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_EH__DispatchEx+) ()
XXlib/arm/libSafeAreaDemo.Android.so (RhThrowEx+) ()
XXlib/arm/libSafeAreaDemo.Android.so (RhpThrowEx+) ()
XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__EnsureClassConstructorRun+) ()
XXlib/arm/libSafeAreaDemo.Android.so (RhpCallCatchFunclet+) ()
XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__CheckStaticClassConstructionReturnNonGCStaticBase+) ()
XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android_Runtime_Android_Runtime_AndroidRuntimeInternal__WaitForBridgeProcessing+) ()
XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android_Android_Runtime_JNINativeWrapper__Wrap_JniMarshal_PPLL_L+) ()
XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android__JniMarshal_PPLL_L__InvokeClosedStaticThunk+) ()
XXlib/arm/libSafeAreaDemo.Android.so (Internal_CompilerGenerated__Module___<ReverseDelegateStub>Mono_Android__JniMarshal_PPLL_L+) ()
/apex/com.android.art/lib/libart.so (art_quick_generic_jni_trampoline+) ()

The cause of this crash is that
JNIINativeWrapper.Wrap_JniMarshal_PPLL_L() called AndroidRuntimeInternal.WaitForBridgeProcessing(), which in turn triggers the AndroidRuntimeInternal static constructor, which throws unless on MonoVM or CoreCLR, of which NativeAOT is neither.

Fix this by updating JNINativeWrapper.g.tt to call JNIEnv.ValueManager?.WaitForGCBridgeProcessing() instead of AndroidRuntimeInternal.WaitForBridgeProcessing(). This allows NativeAOT to control what actually happens.

Aside: this project includes T4 Text Templates. To regenerate the output files without involving Visual Studio, you can install the dotnet-t4 tool:

$ dotnet tool install --global dotnet-t4

then run it separately for each .tt file:

$HOME/.dotnet/tools/t4 -o src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs \
  src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt

Fixes: #10126

Context: 4b158ce
Context: dotnet/java-interop@356485e
Context: 32cff43

NativeAOT currently requires new binding assemblies, bindings
which do not use `JNINativeWrapper.CreateDelegate()`, as one of the
code paths within `JNINativeWrapper.CreateDelegate()` uses
`System.Reflection.Emit`, which does not exist in NativeAOT.
dotnet/java-interop@356485ee contains the `generator` changes needed
to avoid `JNINativeWrapper.CreateDelegate()`.

However, there is one situation in which that "requirement" is (kinda)
*conditional*: commit 32cff43 added "builtin" JNI wrapper methods,
avoiding the need for System.Reflection.Emit when overriding
Java methods that match a builtin signature.

This means existing binding assemblies can work, right?

Kinda, yeah!  So long as you *only* override methods which match a
builtin signature.  Which was enough to get MAUI going atop
NativeAOT *without* rebuilding all of dotnet/android-libraries…

However, for not-entirely-understood reasons, sometimes using a
builtin JNI wrapper method would cause the app to crash:

	digest=============com.avalonia.safeareademo /apex/com.android.runtime/lib/bionic/libc.so (abort+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (SystemNative_Abort+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_Interop_Sys__Abort+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_RuntimeExceptionHelpers__FailFast+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (RuntimeFailFast+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_EH__UnhandledExceptionFailFastViaClasslib+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_EH__DispatchEx+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (RhThrowEx+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (RhpThrowEx+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__EnsureClassConstructorRun+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (RhpCallCatchFunclet+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__CheckStaticClassConstructionReturnNonGCStaticBase+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android_Runtime_Android_Runtime_AndroidRuntimeInternal__WaitForBridgeProcessing+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android_Android_Runtime_JNINativeWrapper__Wrap_JniMarshal_PPLL_L+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android__JniMarshal_PPLL_L__InvokeClosedStaticThunk+) ()
	XXlib/arm/libSafeAreaDemo.Android.so (Internal_CompilerGenerated__Module___<ReverseDelegateStub>Mono_Android__JniMarshal_PPLL_L+) ()
	/apex/com.android.art/lib/libart.so (art_quick_generic_jni_trampoline+) ()

The cause of this crash is that
`JNIINativeWrapper.Wrap_JniMarshal_PPLL_L()` called
`AndroidRuntimeInternal.WaitForBridgeProcessing()`, which in turn
triggers the `AndroidRuntimeInternal` static constructor, which
*throws* unless on MonoVM or CoreCLR, of which NativeAOT is neither.

Fix this by updating `JNINativeWrapper.g.tt` to call
`JNIEnv.ValueManager?.WaitForGCBridgeProcessing()` instead of
`AndroidRuntimeInternal.WaitForBridgeProcessing()`.  This allows
NativeAOT to control what actually happens.

Aside: this project includes [T4 Text Templates][0].  To regenerate
the output files *without involving Visual Studio*, you can install
the [`dotnet-t4`][1] tool:

	$ dotnet tool install --global dotnet-t4

then run it separately for each `.tt` file:

	$HOME/.dotnet/tools/t4 -o src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs \
	  src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt

[0]: https://learn.microsoft.com/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022
[1]: https://www.nuget.org/packages/dotnet-t4/
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a crash on NativeAOT by replacing calls to AndroidRuntimeInternal.WaitForBridgeProcessing() with JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing(), ensuring that the proper bridge processing method is invoked for built-in JNI wrappers.

  • Updated code in T4 template (JNINativeWrapper.g.tt)
  • Updated multiple JNI wrapper methods in JNINativeWrapper.g.cs
  • Modified JNIEnv.WaitForBridgeProcessing() to use the new API

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt Replaced wait call to use JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing()
src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs Updated all JNI wrapper methods to call JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing()
src/Mono.Android/Android.Runtime/JNIEnv.cs Modified WaitForBridgeProcessing() to use the new call for NativeAOT support
Comments suppressed due to low confidence (3)

src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt:271

  • Ensure that JNIEnvInit.ValueManager is always properly initialized in contexts where WaitForGCBridgeProcessing() is required, as using the null-conditional operator may suppress necessary processing if ValueManager is null. Consider adding inline documentation to clarify this behavior.
JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing ();

src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:20

  • Verify that the substitution of AndroidRuntimeInternal.WaitForBridgeProcessing() with JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing() across all JNI wrapper methods meets the intended behavior in all runtime scenarios, particularly in NativeAOT where proper bridge processing is critical.
JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing ();

src/Mono.Android/Android.Runtime/JNIEnv.cs:121

  • Confirm that replacing the direct call with the null-conditional access does not unintentionally bypass necessary wait logic when ValueManager might be null. Inline comments or documentation could help explain this behavior for future maintainers.
JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing ();

@jonpryor jonpryor merged commit 856dc34 into main May 10, 2025
59 checks passed
@jonpryor jonpryor deleted the dev/jonp/jonp-use-abstract-WaitForBridgeProcessing branch May 10, 2025 00:06
@github-actions github-actions bot locked and limited conversation to collaborators Jun 9, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NativeAOT - Crash when constructing a Java class with Java->C# callbacks

3 participants