From 32cff4383232d5de156bd6c5d10292fcffa66d50 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 28 Jan 2022 15:45:46 -0600 Subject: [PATCH] [Mono.Android] avoid System.Reflection.Emit usage for common calls (#6657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration Context: b7a368a27667c69117f64be81050403f2d5c8560 Context: https://github.com/xamarin/xamarin-android/pull/4877 Context: https://github.com/xamarin/xamarin-android/pull/4927#issuecomment-875864999 In order for Java code to call C# code, [`JNIEnv::RegisterNatives()][0] must be invoked, providing an array of `JNINativeMethod` structures, each of which contains a function pointer to invoke, kept in `JNINativeMethod::fnPtr`. Fortunately, delegates marshal as function pointers, and there is a bunch of `generator`-emitted infrastructure and coordination with Java Callable Wrappers to eventually obtain a Delegate instance to provide `JNIEnv::RegisterNatives()`. There is one deficiency in the `generator`-emitted infrastructure: it doesn't deal with C# exceptions. However, exceptions "can't" cross the JNI boundary (see b7a368a2 for an example of the breakage that results when exceptions do cross the boundary!), except when we *do* want exceptions to cross the JNI boundary ("improved" IDE first chance exception experience; see xamarin/xamarin-android#4877). This "we want to catch exceptions, except when we don't" scenario has existed since the very beginning. As "the very beginning" predates [C# 4 exception filters][1], there wasn't a way for `generator` output to "selectively `catch` exceptions". We squared this circle by using `System.Reflection.Emit`: 1. During Java Callable Wrapper registration, we lookup the "marshal method getter" as provided to the `Runtime.register()` invocation, e.g. `Android.App.Activity.GetOnCreate_Landroid_os_Bundle_Handler()`. 2. `GetOnCreate_Landroid_os_Bundle_Handler()` is `generator` output, and contains a `JNINativeWrapper.CreateDelegate()` invocation: cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_); 3. `JNINativeWrapper.CreateDelegate()` uses `System.Reflection.Emit` to create a new delegate instance which *wraps* the marshal method `Activity.n_OnCreate_Landroid_os_Bundle()` in a `try`/*filtered* `catch` block and marshals the exception to Java; `JNINativeWrapper.CreateDelegate()` effectively returns: bool _run_catch_if_debugger_not_attached (Exception e) { if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) { JNIEnv.mono_unhandled_exception (e); return false; } return true; } _JniMarshal_PPL_V result = (jnienv, native__this, native_savedInstanceState) => { JNIEnv.WaitForBridgeProcessing (); try { Activity.n_OnCreate_Landroid_os_Bundle_ (jnienv, native__this, native_savedInstanceState); } catch (Exception e) when (_run_catch_if_debugger_not_attached (e)) { AndroidEnvironment.UnhandledException (e); if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) throw; } }; return result; Again, this was C# 2.0 at the time, so C# 4 exception filters couldn't be used, thus the need for `System.Reflection.Emit`, so that [`ILGenerator.BeginExceptionFilterBLock()`][2] could be used (the support for which is a Mono extension). After this point, use of `System.Reflection.Emit` was part of the implicit ABI between Xamarin.Android and binding assemblies. While `generator` *could* be updated to *itself* emit the `try`/`catch` block with exception filters, that would only work for binding assemblies released *after* that `generator` fix. The `System.Reflection.Emit` wrapper *can't* be skipped without breaking semantic compatibility, *or* without allowing C# exceptions to always pass through a JNI boundary, which would be Bad™. The use of `System.Refleciton.Emit` is a Known Problem™, and something we'd *like* to remove. (Hence the [`jnimarshalmethod-gen`][3] explorations…) With that background out of the way… Let us turn our attention to the `dotnet new maui` template. The default MAUI template hits `JNINativeWrapper.CreateDelegate()` 58 times during process startup, and we were wondering if we could selectively improve these particular invocations, without needing to re-think the entire "marshal method" infrastructure. *Partially specialize* `JNINativeWrapper.CreateDelegate()` for the following delegate types: * `_JniMarshal_PP_V` * `_JniMarshal_PPI_V` * `_JniMarshal_PPL_L` * `_JniMarshal_PPL_V` * `_JniMarshal_PPL_Z` * `_JniMarshal_PPII_V` * `_JniMarshal_PPLI_V` * `_JniMarshal_PPLL_V` * `_JniMarshal_PPLL_Z` * `_JniMarshal_PPIIL_V` * `_JniMarshal_PPILL_V` * `_JniMarshal_PPLIL_Z` * `_JniMarshal_PPLLL_L` * `_JniMarshal_PPLLL_Z` * `_JniMarshal_PPIIII_V` * `_JniMarshal_PPLLLL_V` * `_JniMarshal_PPLIIII_V` * `_JniMarshal_PPZIIII_V` * `_JniMarshal_PPLIIIIIIII_V` This is done via use of a T4 template, which generates `JNINativeWrapper.CreateBuiltInDelegate()`, and `JNINativeWrapper.CreateDelegate()` is updated to call `CreateBuiltInDelegate()`: partial class JNINativeWrapper { static Delegate? CreateBuiltInDelegate (Delegate dlg, Type delegateType) { switch (delegateType.Name) { case "_JniMarshal_PP_V": return … case "_JniMarshal_PPI_V": return … … } return null; } public static Delegate CreateDelegate (Delegate dlg) { … var builtin = CreateBuiltInDelegate (dlg, dlg.GetType (); if (builtin != null) return builtin; … } } This avoids use of `System.Reflection.Emit` for the specified types. Other changes: * Update `TypeManager.GetActivateHandler()` to use `_JniMarshal_PPLLLL_V` instead of `Action`, so the fast path can be used. * Added a log message for `adb shell septprop debug.mono.log assembly`: Falling back to System.Reflection.Emit for delegate type '{delegateType}': {dlg.Method} * I was also able to remove `mono_unhandled_exception_method` from `JNINativeWrapper` as we already has this value in `JNIEnv`. ~~ Results ~~ Testing `dotnet new maui` with version: msbuild Xamarin.Android.sln -t:InstallMaui -bl -p:MauiVersion=6.0.200-preview.13.2536 A `Release` build on a Pixel 5 device, total startup time: | Startup | Average (ms) | Std Err (ms) | Std Dev (ms) | | --------- | ------------: | ------------: | ------------: | | Before | 1106.3 | 6.919 | 21.879 | | After | 1078.8 | 5.438 | 17.197 | This might save ~35ms on average? If I time the message for one call, [such as][4]: I monodroid-timing: Runtime.register: registering type `Microsoft.Maui.MauiApplication, Microsoft.Maui, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null` I monodroid-timing: Runtime.register: end time; elapsed: 0s:17::794845 The result is: | One Call | Average (ms) | Std Err (ms) | Std Dev (ms) | | --------- | ------------: | ------------: | ------------: | | Before | 23.925 | 0.050 | 0.159 | | After | 18.723 | 0.094 | 0.298 | Saving ~5.8ms for this one call. `.apk` size difference for `dotnet new android`: % apkdiff -f before.apk after.apk Size difference in bytes ([*1] apk1 only, [*2] apk2 only): + 3,390 assemblies/assemblies.blob + 54 assemblies/assemblies.x86_64.blob - 4 assemblies/assemblies.arm64_v8a.blob - 15 assemblies/assemblies.x86.blob - 65 assemblies/assemblies.armeabi_v7a.blob Summary: + 3,360 Other entries 0.03% (of 10,526,432) + 0 Dalvik executables 0.00% (of 7,816,392) + 0 Shared libraries 0.00% (of 18,414,404) + 4,096 Package size difference 0.02% (of 21,006,128) We're looking at a ~4KB size increase for this partial specialization. [0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives [1]: https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/exception-handling#catch-blocks [2]: https://docs.microsoft.com/is-is/dotnet/api/system.reflection.emit.ilgenerator.beginexceptfilterblock?view=net-6.0 [3]: http://github.com/xamarin/Java.Interop/commit/c8f3e51a6cfd78bdce89e2429efae4495481f57b [4]: https://github.com/dotnet/maui/blob/bfba62ed796d3416c4fcaa7cfbea86dc8d5e04c2/src/Compatibility/ControlGallery/src/Android/MainApplication.cs --- src/Mono.Android/Android.Runtime/JNIEnv.cs | 18 +- .../Android.Runtime/JNINativeWrapper.cs | 26 +- .../Android.Runtime/JNINativeWrapper.g.cs | 272 ++++++++++++++++++ .../Android.Runtime/JNINativeWrapper.g.tt | 71 +++++ src/Mono.Android/Java.Interop/TypeManager.cs | 7 +- src/Mono.Android/Mono.Android.csproj | 9 + .../BuildReleaseArm64SimpleLegacy.apkdesc | 22 +- 7 files changed, 390 insertions(+), 35 deletions(-) create mode 100644 src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs create mode 100644 src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index f720fab843e..4814585bd7c 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -56,7 +56,7 @@ public static partial class JNIEnv { internal static bool PropagateExceptions; internal static bool IsRunningOnDesktop; - internal static bool LogTypemapMissStackTrace; + internal static bool LogAssemblyCategory; static AndroidRuntime? androidRuntime; static BoundExceptionType BoundExceptionType; @@ -67,7 +67,7 @@ public static partial class JNIEnv { internal static AndroidValueManager? AndroidValueManager; [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] - extern static void monodroid_log (LogLevel level, LogCategories category, string message); + internal extern static void monodroid_log (LogLevel level, LogCategories category, string message); [DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)] internal extern static IntPtr monodroid_timing_start (string? message); @@ -149,7 +149,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) partial_timing_sequence = monodroid_timing_start (null); } - LogTypemapMissStackTrace = (args->logCategories & (uint)LogCategories.Assembly) != 0; + LogAssemblyCategory = (args->logCategories & (uint)LogCategories.Assembly) != 0; gref_gc_threshold = args->grefGcThreshold; @@ -246,6 +246,7 @@ static void ManualJavaObjectDispose (Java.Lang.Object obj) #else // NETCOREAPP internal static Action mono_unhandled_exception = null!; #endif // NETCOREAPP + internal static MethodInfo? mono_unhandled_exception_method = null; #if !NETCOREAPP static Action AppDomain_DoUnhandledException = null!; @@ -254,10 +255,13 @@ static void ManualJavaObjectDispose (Java.Lang.Object obj) static void Initialize () { if (mono_unhandled_exception == null) { - var mono_UnhandledException = typeof (System.Diagnostics.Debugger) + mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger) .GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); - if (mono_UnhandledException != null) - mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), mono_UnhandledException); + if (mono_unhandled_exception_method != null) + mono_unhandled_exception = (Action) Delegate.CreateDelegate (typeof(Action), mono_unhandled_exception_method); + } + if (mono_unhandled_exception_method == null && mono_unhandled_exception != null) { + mono_unhandled_exception_method = mono_unhandled_exception.Method; } #if !NETCOREAPP @@ -737,7 +741,7 @@ internal static void LogTypemapTrace (StackTrace st) } if (ret == IntPtr.Zero) { - if (LogTypemapMissStackTrace) { + if (LogAssemblyCategory) { monodroid_log (LogLevel.Warn, LogCategories.Default, $"typemap: failed to map managed type to Java type: {type.AssemblyQualifiedName} (Module ID: {type.Module.ModuleVersionId}; Type token: {type.MetadataToken})"); LogTypemapTrace (new StackTrace (true)); } diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs index 88b1e3b110b..1d09ccf9772 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs @@ -5,9 +5,8 @@ using System.Threading; namespace Android.Runtime { - public static class JNINativeWrapper { + public static partial class JNINativeWrapper { - static MethodInfo? mono_unhandled_exception_method; static MethodInfo? exception_handler_method; static MethodInfo? wait_for_bridge_processing_method; @@ -15,15 +14,7 @@ static void get_runtime_types () { if (exception_handler_method != null) return; -#if MONOANDROID1_0 - mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger).GetMethod ( - "Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); - if (mono_unhandled_exception_method == null) - AndroidEnvironment.FailFast ("Cannot find System.Diagnostics.Debugger.Mono_UnhandledException"); -#endif -#if NETCOREAPP - mono_unhandled_exception_method = JNIEnv.mono_unhandled_exception.Method; -#endif // NETCOREAPP + exception_handler_method = typeof (AndroidEnvironment).GetMethod ( "UnhandledException", BindingFlags.NonPublic | BindingFlags.Static); if (exception_handler_method == null) @@ -45,6 +36,15 @@ public static Delegate CreateDelegate (Delegate dlg) get_runtime_types (); + var delegateType = dlg.GetType (); + var result = CreateBuiltInDelegate (dlg, delegateType); + if (result != null) + return result; + + if (JNIEnv.LogAssemblyCategory) { + JNIEnv.monodroid_log (LogLevel.Debug, LogCategories.Assembly, $"Falling back to System.Reflection.Emit for delegate type '{delegateType}': {dlg.Method}"); + } + var ret_type = dlg.Method.ReturnType; var parameters = dlg.Method.GetParameters (); var param_types = new Type [parameters.Length]; @@ -73,10 +73,10 @@ public static Delegate CreateDelegate (Delegate dlg) ig.Emit (OpCodes.Leave, label); bool filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions; - if (filter && mono_unhandled_exception_method != null) { + if (filter && JNIEnv.mono_unhandled_exception_method != null) { ig.BeginExceptFilterBlock (); - ig.Emit (OpCodes.Call, mono_unhandled_exception_method); + ig.Emit (OpCodes.Call, JNIEnv.mono_unhandled_exception_method); ig.Emit (OpCodes.Ldc_I4_1); ig.BeginCatchBlock (null!); } else { diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs new file mode 100644 index 00000000000..31dc9e1e5b8 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs @@ -0,0 +1,272 @@ +using System; +using System.Diagnostics; + +namespace Android.Runtime +{ + public static partial class JNINativeWrapper + { + static bool _unhandled_exception (Exception e) + { + if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) { + JNIEnv.mono_unhandled_exception (e); + return false; + } + return true; + } + + private static Delegate CreateBuiltInDelegate (Delegate dlg, Type delegateType) + { + switch (delegateType.Name) { + case nameof (_JniMarshal_PP_V): { + _JniMarshal_PP_V callback = (_JniMarshal_PP_V) Delegate.CreateDelegate (typeof (_JniMarshal_PP_V), dlg.Target, dlg.Method); + _JniMarshal_PP_V result = (jnienv, klazz) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPI_V): { + _JniMarshal_PPI_V callback = (_JniMarshal_PPI_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPI_V), dlg.Target, dlg.Method); + _JniMarshal_PPI_V result = (jnienv, klazz, p0) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPL_L): { + _JniMarshal_PPL_L callback = (_JniMarshal_PPL_L) Delegate.CreateDelegate (typeof (_JniMarshal_PPL_L), dlg.Target, dlg.Method); + _JniMarshal_PPL_L result = (jnienv, klazz, p0) => { + JNIEnv.WaitForBridgeProcessing (); + try { + return callback (jnienv, klazz, p0); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + return default; + } + }; + return result; + } + case nameof (_JniMarshal_PPL_V): { + _JniMarshal_PPL_V callback = (_JniMarshal_PPL_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPL_V), dlg.Target, dlg.Method); + _JniMarshal_PPL_V result = (jnienv, klazz, p0) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPL_Z): { + _JniMarshal_PPL_Z callback = (_JniMarshal_PPL_Z) Delegate.CreateDelegate (typeof (_JniMarshal_PPL_Z), dlg.Target, dlg.Method); + _JniMarshal_PPL_Z result = (jnienv, klazz, p0) => { + JNIEnv.WaitForBridgeProcessing (); + try { + return callback (jnienv, klazz, p0); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + return default; + } + }; + return result; + } + case nameof (_JniMarshal_PPII_V): { + _JniMarshal_PPII_V callback = (_JniMarshal_PPII_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPII_V), dlg.Target, dlg.Method); + _JniMarshal_PPII_V result = (jnienv, klazz, p0, p1) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPLI_V): { + _JniMarshal_PPLI_V callback = (_JniMarshal_PPLI_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPLI_V), dlg.Target, dlg.Method); + _JniMarshal_PPLI_V result = (jnienv, klazz, p0, p1) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPLL_V): { + _JniMarshal_PPLL_V callback = (_JniMarshal_PPLL_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPLL_V), dlg.Target, dlg.Method); + _JniMarshal_PPLL_V result = (jnienv, klazz, p0, p1) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPLL_Z): { + _JniMarshal_PPLL_Z callback = (_JniMarshal_PPLL_Z) Delegate.CreateDelegate (typeof (_JniMarshal_PPLL_Z), dlg.Target, dlg.Method); + _JniMarshal_PPLL_Z result = (jnienv, klazz, p0, p1) => { + JNIEnv.WaitForBridgeProcessing (); + try { + return callback (jnienv, klazz, p0, p1); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + return default; + } + }; + return result; + } + case nameof (_JniMarshal_PPIIL_V): { + _JniMarshal_PPIIL_V callback = (_JniMarshal_PPIIL_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPIIL_V), dlg.Target, dlg.Method); + _JniMarshal_PPIIL_V result = (jnienv, klazz, p0, p1, p2) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1, p2); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPILL_V): { + _JniMarshal_PPILL_V callback = (_JniMarshal_PPILL_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPILL_V), dlg.Target, dlg.Method); + _JniMarshal_PPILL_V result = (jnienv, klazz, p0, p1, p2) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1, p2); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPLIL_Z): { + _JniMarshal_PPLIL_Z callback = (_JniMarshal_PPLIL_Z) Delegate.CreateDelegate (typeof (_JniMarshal_PPLIL_Z), dlg.Target, dlg.Method); + _JniMarshal_PPLIL_Z result = (jnienv, klazz, p0, p1, p2) => { + JNIEnv.WaitForBridgeProcessing (); + try { + return callback (jnienv, klazz, p0, p1, p2); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + return default; + } + }; + return result; + } + case nameof (_JniMarshal_PPLLL_L): { + _JniMarshal_PPLLL_L callback = (_JniMarshal_PPLLL_L) Delegate.CreateDelegate (typeof (_JniMarshal_PPLLL_L), dlg.Target, dlg.Method); + _JniMarshal_PPLLL_L result = (jnienv, klazz, p0, p1, p2) => { + JNIEnv.WaitForBridgeProcessing (); + try { + return callback (jnienv, klazz, p0, p1, p2); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + return default; + } + }; + return result; + } + case nameof (_JniMarshal_PPLLL_Z): { + _JniMarshal_PPLLL_Z callback = (_JniMarshal_PPLLL_Z) Delegate.CreateDelegate (typeof (_JniMarshal_PPLLL_Z), dlg.Target, dlg.Method); + _JniMarshal_PPLLL_Z result = (jnienv, klazz, p0, p1, p2) => { + JNIEnv.WaitForBridgeProcessing (); + try { + return callback (jnienv, klazz, p0, p1, p2); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + return default; + } + }; + return result; + } + case nameof (_JniMarshal_PPIIII_V): { + _JniMarshal_PPIIII_V callback = (_JniMarshal_PPIIII_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPIIII_V), dlg.Target, dlg.Method); + _JniMarshal_PPIIII_V result = (jnienv, klazz, p0, p1, p2, p3) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1, p2, p3); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPLLLL_V): { + _JniMarshal_PPLLLL_V callback = (_JniMarshal_PPLLLL_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPLLLL_V), dlg.Target, dlg.Method); + _JniMarshal_PPLLLL_V result = (jnienv, klazz, p0, p1, p2, p3) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1, p2, p3); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPLIIII_V): { + _JniMarshal_PPLIIII_V callback = (_JniMarshal_PPLIIII_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPLIIII_V), dlg.Target, dlg.Method); + _JniMarshal_PPLIIII_V result = (jnienv, klazz, p0, p1, p2, p3, p4) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1, p2, p3, p4); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPZIIII_V): { + _JniMarshal_PPZIIII_V callback = (_JniMarshal_PPZIIII_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPZIIII_V), dlg.Target, dlg.Method); + _JniMarshal_PPZIIII_V result = (jnienv, klazz, p0, p1, p2, p3, p4) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1, p2, p3, p4); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + case nameof (_JniMarshal_PPLIIIIIIII_V): { + _JniMarshal_PPLIIIIIIII_V callback = (_JniMarshal_PPLIIIIIIII_V) Delegate.CreateDelegate (typeof (_JniMarshal_PPLIIIIIIII_V), dlg.Target, dlg.Method); + _JniMarshal_PPLIIIIIIII_V result = (jnienv, klazz, p0, p1, p2, p3, p4, p5, p6, p7, p8) => { + JNIEnv.WaitForBridgeProcessing (); + try { + callback (jnienv, klazz, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + + } + }; + return result; + } + default: + return null; + } + } + } +} diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt new file mode 100644 index 00000000000..d757c2fad5a --- /dev/null +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt @@ -0,0 +1,71 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +<# +var delegateTypes = new [] { + new { Type = "_JniMarshal_PP_V", Signature = "(jnienv, klazz)", Return = false }, + new { Type = "_JniMarshal_PPI_V", Signature = "(jnienv, klazz, p0)", Return = false }, + new { Type = "_JniMarshal_PPL_L", Signature = "(jnienv, klazz, p0)", Return = true }, + new { Type = "_JniMarshal_PPL_V", Signature = "(jnienv, klazz, p0)", Return = false }, + new { Type = "_JniMarshal_PPL_Z", Signature = "(jnienv, klazz, p0)", Return = true }, + new { Type = "_JniMarshal_PPII_V", Signature = "(jnienv, klazz, p0, p1)", Return = false }, + new { Type = "_JniMarshal_PPLI_V", Signature = "(jnienv, klazz, p0, p1)", Return = false }, + new { Type = "_JniMarshal_PPLL_V", Signature = "(jnienv, klazz, p0, p1)", Return = false }, + new { Type = "_JniMarshal_PPLL_Z", Signature = "(jnienv, klazz, p0, p1)", Return = true }, + new { Type = "_JniMarshal_PPIIL_V", Signature = "(jnienv, klazz, p0, p1, p2)", Return = false }, + new { Type = "_JniMarshal_PPILL_V", Signature = "(jnienv, klazz, p0, p1, p2)", Return = false }, + new { Type = "_JniMarshal_PPLIL_Z", Signature = "(jnienv, klazz, p0, p1, p2)", Return = true }, + new { Type = "_JniMarshal_PPLLL_L", Signature = "(jnienv, klazz, p0, p1, p2)", Return = true }, + new { Type = "_JniMarshal_PPLLL_Z", Signature = "(jnienv, klazz, p0, p1, p2)", Return = true }, + new { Type = "_JniMarshal_PPIIII_V", Signature = "(jnienv, klazz, p0, p1, p2, p3)", Return = false }, + new { Type = "_JniMarshal_PPLLLL_V", Signature = "(jnienv, klazz, p0, p1, p2, p3)", Return = false }, + new { Type = "_JniMarshal_PPLIIII_V", Signature = "(jnienv, klazz, p0, p1, p2, p3, p4)", Return = false }, + new { Type = "_JniMarshal_PPZIIII_V", Signature = "(jnienv, klazz, p0, p1, p2, p3, p4)", Return = false }, + new { Type = "_JniMarshal_PPLIIIIIIII_V", Signature = "(jnienv, klazz, p0, p1, p2, p3, p4, p5, p6, p7, p8)", Return = false }, +}; +#> +using System; +using System.Diagnostics; + +namespace Android.Runtime +{ + public static partial class JNINativeWrapper + { + static bool _unhandled_exception (Exception e) + { + if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) { + JNIEnv.mono_unhandled_exception (e); + return false; + } + return true; + } + + private static Delegate CreateBuiltInDelegate (Delegate dlg, Type delegateType) + { + switch (delegateType.Name) { +<# +foreach (var info in delegateTypes) { +#> + case nameof (<#= info.Type #>): { + <#= info.Type #> callback = (<#= info.Type #>) Delegate.CreateDelegate (typeof (<#= info.Type #>), dlg.Target, dlg.Method); + <#= info.Type #> result = <#= info.Signature #> => { + JNIEnv.WaitForBridgeProcessing (); + try { + <#= info.Return ? "return " : "" #>callback <#= info.Signature #>; + } catch (Exception e) when (_unhandled_exception (e)) { + AndroidEnvironment.UnhandledException (e); + <#= info.Return ? "return default;" : "" #> + } + }; + return result; + } +<# +} +#> + default: + return null; + } + } + } +} diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 5476bd15f29..9dbaed1c368 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -97,12 +97,11 @@ public int Compare (string x, string y) return mappings [i].Substring (c+1); } - static Action? cb_activate; + static _JniMarshal_PPLLLL_V? cb_activate; internal static Delegate GetActivateHandler () { if (cb_activate == null) - cb_activate = (Action) JNINativeWrapper.CreateDelegate ( - (Action) n_Activate); + cb_activate = (_JniMarshal_PPLLLL_V) JNINativeWrapper.CreateDelegate ((_JniMarshal_PPLLLL_V) n_Activate); return cb_activate; } @@ -220,7 +219,7 @@ static Exception CreateJavaLocationException () if (!JNIEnv.IsRunningOnDesktop) { // Miss message is logged in the native runtime - if (JNIEnv.LogTypemapMissStackTrace) + if (JNIEnv.LogAssemblyCategory) JNIEnv.LogTypemapTrace (new System.Diagnostics.StackTrace (true)); return null; } diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 67be0aac176..41419fc4984 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -125,6 +125,10 @@ + + TextTemplatingFileGenerator + JNINativeWrapper.g.cs + @@ -241,6 +245,11 @@ + + True + True + JNINativeWrapper.g.tt + diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc index ad0f9acde7f..21cfa18f851 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc @@ -5,25 +5,25 @@ "Size": 2604 }, "assemblies/Java.Interop.dll": { - "Size": 67759 + "Size": 67924 }, "assemblies/Mono.Android.dll": { - "Size": 251399 + "Size": 255765 }, "assemblies/mscorlib.dll": { - "Size": 769324 + "Size": 769156 }, "assemblies/System.Core.dll": { - "Size": 28187 + "Size": 28199 }, "assemblies/System.dll": { - "Size": 9171 + "Size": 9180 }, "assemblies/UnnamedProject.dll": { - "Size": 2866 + "Size": 2880 }, "classes.dex": { - "Size": 345208 + "Size": 347796 }, "lib/arm64-v8a/libmono-btls-shared.so": { "Size": 1613872 @@ -32,16 +32,16 @@ "Size": 707024 }, "lib/arm64-v8a/libmonodroid.so": { - "Size": 286736 + "Size": 294736 }, "lib/arm64-v8a/libmonosgen-2.0.so": { - "Size": 4037584 + "Size": 4030000 }, "lib/arm64-v8a/libxa-internal-api.so": { "Size": 65624 }, "lib/arm64-v8a/libxamarin-app.so": { - "Size": 20032 + "Size": 20592 }, "META-INF/ANDROIDD.RSA": { "Size": 1213 @@ -74,5 +74,5 @@ "Size": 1724 } }, - "PackageSize": 3983060 + "PackageSize": 3991252 } \ No newline at end of file