Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Marshal methods, Part 1 of ? #7004

Merged
merged 14 commits into from
Jun 6, 2022
Merged

Conversation

grendello
Copy link
Contributor

@grendello grendello commented May 16, 2022

This PR brings the lowest level "plumbing" for the future marshal methods implementation.

The code is disabled because it's not ready to work for anyone else except me, but I'd
like to split the code up into chunks that are easier to review, as the entire thing
is likely to be very big.

The code just needs to compile and stay out of the way.

Marshal methods are a feature designed to replace the current mechanism of "registering"
managed types with the Java runtime (via JNI). The way this is currently done, is the
JI binding generator generates 3 managed methods per single overridable Java class member,
for instance Android.App.Activity::OnCreate:

static Delegate? cb_onCreate_Landroid_os_Bundle_;
static Delegate GetOnCreate_Landroid_os_Bundle_Handler ()
{
  if (cb_onCreate_Landroid_os_Bundle_ == null)
    cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_);
  return cb_onCreate_Landroid_os_Bundle_;
}

static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
{
  var __this = global::Java.Lang.Object.GetObject<Android.App.Activity> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
  var savedInstanceState = global::Java.Lang.Object.GetObject<Android.OS.Bundle> (native_savedInstanceState, JniHandleOwnership.DoNotTransfer);
  __this.OnCreate (savedInstanceState);
}

// Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='onCreate' and count(parameter)=1 and parameter[1][@type='android.os.Bundle']]"
[Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")]
protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState)
{
  const string __id = "onCreate.(Landroid/os/Bundle;)V";
  try {
    JniArgumentValue* __args = stackalloc JniArgumentValue [1];
    __args [0] = new JniArgumentValue ((savedInstanceState == null) ? IntPtr.Zero : ((global::Java.Lang.Object) savedInstanceState).Handle);
    _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args);
  } finally {
    global::System.GC.KeepAlive (savedInstanceState);
  }
}

Another generator, JavaCallableWrappersGenerator, is used to output Java code whenever the application
overrides any of the Java members, e.g. for the same OnCreate above:

public static final String __md_methods;
static {
  __md_methods = 
    "n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" +
    "";
    mono.android.Runtime.register ("HelloAndroid.MainActivity, HelloAndroid", MainActivity.class, __md_methods);
}

public void onCreate (android.os.Bundle p0)
{
  n_onCreate (p0);
}

private native void n_onCreate (android.os.Bundle p0);

From there, the mono.android.Runtime.register call is a function defined in the native Xamarin.Android runtime
which takes the method descriptions and, when called by Java, passes them to the managed land which iterates
over the __md_methods array, processing each string to register (using JNI) the method with Java runtime.

Marshal methods will replace the registration mechanism, thus removing the .register call and __md_methods
from the Java side, instead generating a set of native functions in the Xamarin.Android runtime with correct symbol
names that will allow the Java runtime to call the, e.g., n_OnCreate above, inside our native code. This will
remove the need for entire machinery that currently emits CIL code at the managed registration stage.

It will also allow us to remove the delegate backing field and the GetOnCreate_Landroid_os_Bundle_Handler method (and
its counterparts for other overrides) from the generated C# code. The way this is going to work is that the
handler method (n_OnCreate_Landroid_os_Bundle_ above) will be decorated with the [UnmanagedCallersOnly] attribute
which will allow it to be invoked directly from the generated native function (the n_OnCreate implementation in the
example above).

The current plan of how the above modifications are going to work at build time is that the JI C# code generator
will output two versions of the native handler code (the n_OnCreate_Landroid_os_Bundle_ above) where one of them
will have the [UnmanagedCallersOnly] attribute and the other will not. The function decorated with the attribute will
have a slightly different name (since it will otherwise have the same signature), e.g. n_OnCreate_Landroid_os_Bundle_MM.
Then, when the application is built and marshal methods are enabled, the linker will remove the static backing field which
stores the delegate, as well as the GetOnCreate_Landroid_os_Bundle_Handler method, together with the
n_OnCreate_Landroid_os_Bundle_ method. If marshal methods are disabled, however, only the n_OnCreate_Landroid_os_Bundle_MM
will be removed.

@@ -23,6 +23,10 @@
<RollForward>Major</RollForward>
</PropertyGroup>

<PropertyGroup Condition=" '$(MSBuildRuntimeType)' == 'Core' ">
Copy link
Member

Choose a reason for hiding this comment

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

Is building with dotnet build a requirement for this one? When you build inside Visual Studio, it will be Full using .NET Framework MSBuild.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, marshal methods will be NET7+ only

Copy link
Member

Choose a reason for hiding this comment

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

Well, but you can build .NET 7 projects in Visual Studio with .NET Framework MSBuild.

Does this feature require our build to be running under .NET 7?

Copy link
Member

Choose a reason for hiding this comment

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

We can maybe shell out to dotnet to make this work, if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Native portion of the feature uses APIs that aren't available in Mono classic, so I wanted to make sure it's not enabled for classic by accident. However, this properly is here just until I finish all the parts of the feature, it will be
removed in the last PR. It's just easier this way for me to keep the feature disabled in main while I can enable it globally in the branch I'm working in on the next PRs.

@grendello grendello added the do-not-merge PR should not be merged. label May 17, 2022
@jonpryor
Copy link
Member

@grendello: please provide a description of the proposed architecture, how all the pieces fit together, and what the resulting output "looks like" conceptually.

@grendello
Copy link
Contributor Author

@grendello: please provide a description of the proposed architecture, how all the pieces fit together, and what the resulting output "looks like" conceptually.

I can provide a bird's eye view of the architecture, I don't know exactly how the details are going to look. I'd rather leave writing docs till the last commit in the series. Unless you want that high altitude overview?

jonpryor pushed a commit to dotnet/java-interop that referenced this pull request May 19, 2022
…987)

Context: dotnet/android#7004
Context: fb94d59

fb94d59 added code to collect all `override` method descriptors in a
list, so that the future marshal methods code generator can generate
correct code for them.  However, not all gathered descriptors contain
the name of the managed type which declares the overridden methods,
which is data that the native marshal method generator needs.

Update when `JavaCallableWrapperGenerator.OverriddenMethodDescriptors`
is populated so that the marshal method's declaring type is used.
@grendello grendello removed the do-not-merge PR should not be merged. label May 19, 2022
@grendello grendello force-pushed the marshal-methods branch 3 times, most recently from f305db6 to ac1433c Compare May 20, 2022 22:29
#endif

namespace xamarin::android::internal {
enum class MonoImageLoaderContext
Copy link
Member

Choose a reason for hiding this comment

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

I know large fractions of this code aren't even built in this PR, but why have this enum when only AssemblyLoadContext will be supported for marshal methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a different kind of context, I'm not even sure I'm going to use this enum. You can ignore it for now :)

MonoError error;
void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error);
if (ret == nullptr || error.error_code != MONO_ERROR_NONE) {
// TODO: make the error message friendlier somehow (class, method and assembly names)
Copy link
Member

Choose a reason for hiding this comment

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

Silly idea? store that "friendly string" as part of the struct. Alas, that'll make things bigger…

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I thought about that, it might be the best idea

},
"classes.dex": {
"Size": 348440
},
"lib/arm64-v8a/libmonodroid.so": {
"Size": 484512
"Size": 510920
Copy link
Member

Choose a reason for hiding this comment

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

Seems odd that libmonodroid.so is ~26KB larger, yet lots of the new C++ code is not even compiled. Weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A lot more code is inlined now. It's a bit surprising it generates that much of a difference, tbh.

@jonpryor
Copy link
Member

jonpryor commented Jun 6, 2022

[Xamarin.Android.Build.Tasks, monodroid] LLVM Marshal Methods Infra (#7004)

Context: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration

Introduce low-level "plumbing" for future JNI marshal method work.

A JNI marshal method is a [JNI-callable][0] function pointer provided
to [`JNIEnv::RegisterNatives()`][1].  Currently, JNI marshal methods
are provided via the interaction between `generator` and
`JNINativeWrapper.CreateDelegate()`:

  * `generator` emits the "actual" JNI-callable method.
  * `JNINativeWrapper.CreateDelegate()` uses System.Reflection.Emit
    to *wrap* the `generator`-emitted for exception marshaling.
    (Though see also 32cff438.)

JNI marshal methods are needed for all Java-to-C# transitions.

Consider the virtual `Activity.OnCreate()` method:

	partial class Activity {
	  static Delegate? cb_onCreate_Landroid_os_Bundle_;
	  static Delegate GetOnCreate_Landroid_os_Bundle_Handler ()
	  {
	    if (cb_onCreate_Landroid_os_Bundle_ == null)
	      cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_);
	    return cb_onCreate_Landroid_os_Bundle_;
	  }
	
	  static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
	  {
	    var __this = global::Java.Lang.Object.GetObject<Android.App.Activity> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
	    var savedInstanceState = global::Java.Lang.Object.GetObject<Android.OS.Bundle> (native_savedInstanceState, JniHandleOwnership.DoNotTransfer);
	    __this.OnCreate (savedInstanceState);
	  }
	
	  // Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='onCreate' and count(parameter)=1 and parameter[1][@type='android.os.Bundle']]"
	  [Register ("onCreate", "(Landroid/os/Bundle;)V", "GetOnCreate_Landroid_os_Bundle_Handler")]
	  protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) => …
	}

`Activity.n_OnCreate_Landroid_os_Bundle_()` is the JNI marshal method,
responsible for marshaling parameters from JNI values into C# types,
forwarding the method invocation to `Activity.OnCreate()`, and (if
necessary) marshaling the return value back to JNI.

`Activity.GetOnCreate_Landroid_os_Bundle_Handler()` is part of the
type registration infrastructure, providing a `Delegate` instance to
`RegisterNativeMembers .RegisterNativeMembers()`, which is eventually
passed to `JNIEnv::RegisterNatives()`.

While this works, it's not incredibly performant: unless using one of
the optimized delegate types (32cff438 et. al),
System.Reflection.Emit is used to create a wrapper around the marshal
method, which is something we've wanted to avoid doing for years.

Thus, the idea: since we're *already* bundling a native toolchain and
using LLVM-IR to produce `libxamarin-app.so` (b21cbf94, 5271f3e1),
what if we emitted [Java Native Method Names][2] and *skipped* all
the done as part of `Runtime.register()` and
`JNIEnv.RegisterJniNatives()`?

Given:

	class MyActivity : Activity {
	    protected override void OnCreate(Bundle? state) => …
	}

During the build, `libxamarin-app.so` would contain the function:

	JNIEXPORT void JNICALL
	Java_crc…_MyActivity_n_1onCreate (JNIEnv *env, jobject self, jobject state);

During App runtime, the `Runtime.register()` invocation present in
[Java Callable Wrappers][3] would either be omitted or would be a
no-op, and Android/JNI would instead resolve `MyActivity.n_onCreate()`
as `Java_crc…_MyActivity_n_1onCreate()`.

Many of the specifics are still being investigated, and this feature
will be spread across various areas.

We call this effort "LLVM Marshal Methods".

First, prepare the way.  Update `Xamarin.Android.Build.Tasks.dll`
and `src/monodroid` to introduce support for generating JNI marshal
methods into `libxamarin-app.so`.  Most of the added code is
*disabled and hidden* behind `#if ENABLE_MARSHAL_METHODS`.

~~ TODO ~~

Other pieces, in no particular order:

  * Update [`Java.Interop.Tools.JavaCallableWrappers`][4] so that
    static constructors aren't needed when LLVM Marshal Methods
    are used.

  * Update [`generator`][5] so that *two* sets of marshal methods are
    emitted: the current set e.g.
    `Activity.n_OnCreate_Landroid_os_Bundle_()`, and an "overload"
    set which has [`UnmanagedCallersOnlyAttribute`][6].
    LLVM Marshal Methods will be able to directly call these
    "unmanaged marshal methods" without the overhead of
    `mono_runtime_invoke()`; see also f48b97cb.

  * Finish the LLVM code generator so that LLVM Marshal Methods are
    emitted into `libxamarin-app.so`.

  * Update the linker so that much of the earlier marshal method
    infrastructure is removed in Release apps.  When
    LLVM Marshal Methods are used, there is no need for
    `Activity.cb_onCreate_Landroid_os_Bundle_`,
    `Actvitiy.GetOnCreate_Landroid_os_Bundle_Handler()`, or the
    `Activity.n_OnCreate_Landroid_os_Bundle_()` without
    `[UnmanagedCallersOnly]`.

Meanwhile, we cannot remove the existing infrastructure, as the
current System.Reflection.Emit-oriented code is needed for faster app
builds, a desirable feature of Debug configuration builds.

LLVM Marshal Methods will be a Release configuration-only feature.

[0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#native_method_arguments
[1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives
[2]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names
[3]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#java-type-registration
[4]: https://github.com/xamarin/java.interop/tree/main/src/Java.Interop.Tools.JavaCallableWrappers
[5]: https://github.com/xamarin/xamarin-android/wiki/Blueprint#generator
[6]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute?view=net-6.0

@jonpryor jonpryor merged commit e1af958 into dotnet:main Jun 6, 2022
@grendello grendello deleted the marshal-methods branch June 6, 2022 20:45
pjcollins added a commit to pjcollins/android that referenced this pull request Jun 13, 2022
Context: dotnet#7091

We have a couple of BCL tests that seemingly started to fail after
commit e1af958 landed.  Unfortunately PR dotnet#7004 didn't run the BCL test
stage, which may have caught this issue sooner.  Update the triggers for
the BCL tests to include changes to src/monodroid.
pjcollins added a commit that referenced this pull request Jun 13, 2022
Context: #7091

We have a couple of BCL tests that seemingly started to fail after
commit e1af958 landed.  Unfortunately PR #7004 didn't run the BCL test
stage, which may have caught this issue sooner.  Update the triggers for
the BCL tests to include changes to src/monodroid.
jonathanpeppers added a commit to jonathanpeppers/xamarin-android that referenced this pull request Jun 15, 2022
Fixes: dotnet#7085
Fixes: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1554705

In a new Xamarin.Android project, if you go to your `OnCreate()`
method and add a `throw new Exception("test")` VS Mac breaks on a
`NullReferenceException` such as:

    [mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object.
    at Android.Runtime.JNINativeWrapper._unhandled_exception (System.Exception e) [0x0000e] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:12
    at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V (_JniMarshal_PPL_V callback, System.IntPtr jnienv, System.IntPtr klazz, System.IntPtr p0) [0x0001d] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:111
    at (wrapper native-to-managed) Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V(intptr,intptr,intptr)

This does not happen in VS Windows, only VS Mac.

After reviewing my changes in 32cff43, the System.Reflection.Emit
code path does a null check for `mono_unhandled_exception_method`:

    bool  filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions;
    if (filter && JNIEnv.mono_unhandled_exception_method != null) {
        ig.BeginExceptFilterBlock ();

        ig.Emit (OpCodes.Call, JNIEnv.mono_unhandled_exception_method);
        ig.Emit (OpCodes.Ldc_I4_1);
        ig.BeginCatchBlock (null!);
    } else {
        ig.BeginCatchBlock (typeof (Exception));
    }

While the new "fast" code path, does not:

    static bool _unhandled_exception (Exception e)
    {
        if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) {
            JNIEnv.mono_unhandled_exception (e);
            return false;
        }
        return true;
    }

Adding `JNIEnv.mono_unhandled_exception?.Invoke(e)` appears to solve
the problem when I build a local copy of `Mono.Android.dll` and test
it inside VS Mac. I see my exception break properly on the line I
threw the exception.

`mono_unhandled_exception` being null appears to be something
introduced in 2aff4e7. That commit's goal was to not lookup
`mono_unhandled_exception_method` at startup, but wait until an
exception is thrown. Unfortunately, `mono_unhandled_exception_method`
is null at the time that the code S.R.Emitted, so we've had this
behavior for a while!

Since we are looking at reworking this entire system with "marshal
methods" as in dotnet#7004, I think we should simply add the null check for
now. We should probably investigate the sequence of events during
startup & unhandled exceptions when the new system is in place.
jonpryor pushed a commit that referenced this pull request Jun 16, 2022
Fixes: #7085
Fixes: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1554705

In a new Xamarin.Android project, if you go to your `OnCreate()`
method and add a `throw new Exception("test")`, Visual Studio for Mac
breaks on a `NullReferenceException` such as:

	[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object.
	at Android.Runtime.JNINativeWrapper._unhandled_exception (System.Exception e) [0x0000e] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:12
	at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V (_JniMarshal_PPL_V callback, System.IntPtr jnienv, System.IntPtr klazz, System.IntPtr p0) [0x0001d] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:111
	at (wrapper native-to-managed) Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V(intptr,intptr,intptr)

This does not happen in VS Windows, only VS Mac.

After reviewing my changes in 32cff43, the System.Reflection.Emit
code path does a null check for `mono_unhandled_exception_method`:

	bool  filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions;
	if (filter && JNIEnv.mono_unhandled_exception_method != null) {
	    ig.BeginExceptFilterBlock ();

	    ig.Emit (OpCodes.Call, JNIEnv.mono_unhandled_exception_method);
	    ig.Emit (OpCodes.Ldc_I4_1);
	    ig.BeginCatchBlock (null!);
	} else {
	    ig.BeginCatchBlock (typeof (Exception));
	}

While the new "fast" code path, does not:

	static bool _unhandled_exception (Exception e)
	{
	    if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) {
	        JNIEnv.mono_unhandled_exception (e);
	        return false;
	    }
	    return true;
	}

Adding `JNIEnv.mono_unhandled_exception?.Invoke(e)` appears to solve
the problem when I build a local copy of `Mono.Android.dll` and test
it inside VS Mac.  I see my exception break properly on the line I
threw the exception.

`mono_unhandled_exception` being null appears to be something
introduced in 2aff4e7.  That commit's goal was to not lookup
`mono_unhandled_exception_method` at startup, but wait until an
exception is thrown.  Unfortunately, `mono_unhandled_exception_method`
is null at the time that the code S.R.Emitted, so we've had this
behavior for a while!

Since we are looking at reworking this entire system with "marshal
methods" as in #7004, I think we should simply add the null check for
now.  We should probably investigate the sequence of events during
startup & unhandled exceptions when the new system is in place.
jonathanpeppers added a commit that referenced this pull request Jun 17, 2022
Fixes: #7085
Fixes: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1554705

In a new Xamarin.Android project, if you go to your `OnCreate()`
method and add a `throw new Exception("test")`, Visual Studio for Mac
breaks on a `NullReferenceException` such as:

	[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.NullReferenceException: Object reference not set to an instance of an object.
	at Android.Runtime.JNINativeWrapper._unhandled_exception (System.Exception e) [0x0000e] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:12
	at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V (_JniMarshal_PPL_V callback, System.IntPtr jnienv, System.IntPtr klazz, System.IntPtr p0) [0x0001d] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:111
	at (wrapper native-to-managed) Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_V(intptr,intptr,intptr)

This does not happen in VS Windows, only VS Mac.

After reviewing my changes in 32cff43, the System.Reflection.Emit
code path does a null check for `mono_unhandled_exception_method`:

	bool  filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions;
	if (filter && JNIEnv.mono_unhandled_exception_method != null) {
	    ig.BeginExceptFilterBlock ();

	    ig.Emit (OpCodes.Call, JNIEnv.mono_unhandled_exception_method);
	    ig.Emit (OpCodes.Ldc_I4_1);
	    ig.BeginCatchBlock (null!);
	} else {
	    ig.BeginCatchBlock (typeof (Exception));
	}

While the new "fast" code path, does not:

	static bool _unhandled_exception (Exception e)
	{
	    if (Debugger.IsAttached || !JNIEnv.PropagateExceptions) {
	        JNIEnv.mono_unhandled_exception (e);
	        return false;
	    }
	    return true;
	}

Adding `JNIEnv.mono_unhandled_exception?.Invoke(e)` appears to solve
the problem when I build a local copy of `Mono.Android.dll` and test
it inside VS Mac.  I see my exception break properly on the line I
threw the exception.

`mono_unhandled_exception` being null appears to be something
introduced in 2aff4e7.  That commit's goal was to not lookup
`mono_unhandled_exception_method` at startup, but wait until an
exception is thrown.  Unfortunately, `mono_unhandled_exception_method`
is null at the time that the code S.R.Emitted, so we've had this
behavior for a while!

Since we are looking at reworking this entire system with "marshal
methods" as in #7004, I think we should simply add the null check for
now.  We should probably investigate the sequence of events during
startup & unhandled exceptions when the new system is in place.
@github-actions github-actions bot locked and limited conversation to collaborators Jan 24, 2024
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.

3 participants