Skip to content

Remove JNINativeWrapper.CreateDelegate() Usage From Marshal Methods #1258

Closed
@jonpryor

Description

@jonpryor

Context: dotnet/runtime#108211
Context: dotnet/android#9306
Context: dotnet/android#9309
Context: https://github.com/xamarin/monodroid/commit/3e9de5a51bd46263b08365ef18bed1ae472122d8

Consider this marshal method and related infrastructure::

namespace Java.Util.Functions {
	public partial interface IIntConsumerInvoker : global::Java.Lang.Object, IIntConsumer {
		static Delegate? cb_accept_Accept_I_V;
#pragma warning disable 0169
		[global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android24.0")]
		static Delegate GetAccept_IHandler ()
		{
			if (cb_accept_Accept_I_V == null)
				cb_accept_Accept_I_V = JNINativeWrapper.CreateDelegate (new _JniMarshal_PPI_V (n_Accept_I));
			return cb_accept_Accept_I_V;
		}

		[global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android24.0")]
		static void n_Accept_I (IntPtr jnienv, IntPtr native__this, int value)
		{
			var __this = global::Java.Lang.Object.GetObject<Java.Util.Functions.IIntConsumer> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
			__this.Accept (value);
		}
#pragma warning restore 0169
	}
}

Why do we have JNINativeWrapper.CreateDelegate()? (Closely related: why isn't there a try/catch block in n_Accept_I()? Though part of that is "we didn't think of it.")

The answer is around the "unhandled exception" experience when a debugger is attached: the debugger breaks at the "active" throw site. If the type will be caught by a catch block, then you won't get an "unhandled exception" notification, which made customers sad.

Which means that for a good debugger experience, we can't have catch blocks catching exceptions; if we did, then the exceptions would be handled!

Meanwhile, we must catch and marshal exceptions back to Java, otherwise we'll corrupt the JVM during stack unwind!

Where we wound up was a terrible middle:

  • If a debugger is attached, then basically have no exception handling. (Wild oversimplification, but close enough for here.)
  • If a debugger isn't attached, then all exceptions are caught.
  • JNINativeWrapper.CreateDelegate() used System.Reflection.Emit to bridge these two worlds.

However, .NET 9 introduces System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute:

If a .NET Debugger is attached that supports the BreakForUserUnhandledException(Exception) API, the debugger won't break on user-unhandled exceptions when the exception is caught by a method with this attribute, unless BreakForUserUnhandledException(Exception) is called.

Thus, the proposal: update the above marshal method related infrastructure to instead be:

namespace Java.Util.Functions {
	public partial interface IIntConsumerInvoker : global::Java.Lang.Object, IIntConsumer {
		static Delegate? cb_accept_Accept_I_V;
#pragma warning disable 0169
		[global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android24.0")]
		static Delegate GetAccept_IHandler ()
		{
			return cb_accept_Accept_I_V ?? (cb_accept_Accept_I_V = new _JniMarshal_PPI_V (n_Accept_I));
		}

		[global::System.Runtime.Versioning.SupportedOSPlatformAttribute ("android24.0")]
		[global::System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute)]
		static void n_Accept_I (IntPtr jnienv, IntPtr native__this, int value)
		{
			var __envp = new global::Java.Interop.JniTransition (jnienv);
			try {
				var __this = global::Java.Lang.Object.GetObject<Java.Util.Functions.IIntConsumer> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
				__this.Accept (value);
			}
			catch (Exception e) {
				__envp.SetPendingException (e);
				Debugger.BreakForUserUnhandledException (e);
			}
			finally {
				__envp.Dispose ();
			}
		}
#pragma warning restore 0169
	}
}

This entirely removes JNINativeWrapper.CreateDelegate() and in turn System.Reflection.Emit from the marshal method codepath, which should improve app startup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementProposed change to current functionalitygeneratorIssues binding a Java library (generator, class-parse, etc.)

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions