Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

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

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::

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.

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