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::
namespaceJava.Util.Functions{publicpartialinterfaceIIntConsumerInvoker:global::Java.Lang.Object,IIntConsumer{staticDelegate?cb_accept_Accept_I_V;
#pragma warning disable 0169[global::System.Runtime.Versioning.SupportedOSPlatformAttribute("android24.0")]staticDelegateGetAccept_IHandler(){if(cb_accept_Accept_I_V==null)cb_accept_Accept_I_V=JNINativeWrapper.CreateDelegate(new_JniMarshal_PPI_V(n_Accept_I));returncb_accept_Accept_I_V;}[global::System.Runtime.Versioning.SupportedOSPlatformAttribute("android24.0")]staticvoidn_Accept_I(IntPtrjnienv,IntPtrnative__this,intvalue){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 haveJNINativeWrapper.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:
namespaceJava.Util.Functions{publicpartialinterfaceIIntConsumerInvoker:global::Java.Lang.Object,IIntConsumer{staticDelegate?cb_accept_Accept_I_V;
#pragma warning disable 0169[global::System.Runtime.Versioning.SupportedOSPlatformAttribute("android24.0")]staticDelegateGetAccept_IHandler(){returncb_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)]staticvoidn_Accept_I(IntPtrjnienv,IntPtrnative__this,intvalue){var__envp=newglobal::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(Exceptione){__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.
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::
Why do we have
JNINativeWrapper.CreateDelegate()? (Closely related: why isn't there atry/catchblock inn_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"
throwsite. If the type will be caught by acatchblock, 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
catchblocks 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:
JNINativeWrapper.CreateDelegate()usedSystem.Reflection.Emitto bridge these two worlds.However, .NET 9 introduces
System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute:Thus, the proposal: update the above marshal method related infrastructure to instead be:
This entirely removes
JNINativeWrapper.CreateDelegate()and in turnSystem.Reflection.Emitfrom the marshal method codepath, which should improve app startup.