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

[Java.Interop] JavaException supports param override #1293

Merged
merged 5 commits into from
Jan 16, 2025

Conversation

jonpryor
Copy link
Member

Context: dotnet/android#9640
Context: 25de1f3

dotnet/android#9640 completes "unification" with dotnet/java-interop, updating Java.Lang.Object to inherit Java.Interop.JavaObject, just like src/Java.Base.

The biggest impediment with this unification step is that the semantics for referring to a Java instance are quite different:

  • .NET for Android née Xamarin.Android née Mono for Android uses an (IntPtr, JniHandleOwnership) pair to specify the JNI object reference pointer value and what should be done with it. The type of the JNI object reference is inferred by the JniHandleOwnership value, e.g. JniHandleOwnership.TransferLocalRef means that the IntPtr value is a JNI local reference.

  • dotnet/java-interop uses a (ref JniObjectReference, JniObjectReferenceOptions) pair to specify the JNI object reference and what can be done with it. The JniObjectReference value contains the type, but -- due to "pointer trickery"; see 25de1f3 -- it might be so invalid that it cannot be touched at all, and JniObjectReferenceOptions will let us know.

Which brings us to the conundrum: how do we implement the Java.Lang.Throwable(IntPtr, JniHandleOwnership) constructor?

namespace Java.Lang {
    public class Throwable {
        public Throwable(IntPtr handle, JniHandleOwnership transfer);
    }
}

The "obvious and wrong" solution would be to use ref with a temporary…

public Throwable(IntPtr handle, JniHandleOwnership transfer)
    : base (ref new JniObjectReference(handle), …)
{
}

…but that won't compile.

Next, we want to be able to provide message and innerException parameters to System.Exception, but the
JavaException(ref JniObjectReference, JniObjectReferenceOptions) constructor requires a valid JNI Object Reference to do that.

If Java.Lang.Throwable tries to "work around" this by using the JavaException(string, Exception) constructor:

public Throwable(IntPtr handle, JniHandleOwnership transfer)
    : base (_GetMessage (handle), _GetInnerException (handle))
{
    …
}

then the JavaException(string, Exception) constructor will try to invoke the E(String) constructor on the Java side, which:

  1. Is semantically wrong, because we may already have a Java instance in handle, so why are we creating a new instance? But-

  2. This fails for constructor subclasses which don't provide a E(String) constructor! Specifically, the JnienvTest.ActivatedDirectThrowableSubclassesShouldBeRegistered unit test starts crashing for "bizarre" reasons.

Fix these issues by adding the new exception:

namespace Java.Interop {
    partial class JavaException {
	protected JavaException (
                ref JniObjectReference reference,
                JniObjectReferenceOptions transfer,
                JniObjectReference throwableOverride);
    }
}

The new throwableOverride parameter is used to obtain the message and innerException values to provide to the
System.Exception(string, Exception) constructor, allowing reference to be a "do not touch" value.

Additionally, add a protected SetJavaStackTrace() method which is used to set the JavaException.JavaStackTrace property based on a JniObjectReference value.

Context: dotnet/android#9640
Context: 25de1f3

dotnet/android#9640 completes "unification" with dotnet/java-interop,
updating `Java.Lang.Object` to inherit `Java.Interop.JavaObject`,
just like `src/Java.Base`.

The biggest impediment with this unification step is that the
semantics for referring to a Java instance are quite different:

  * .NET for Android née Xamarin.Android née Mono for Android uses
    an `(IntPtr, JniHandleOwnership)` pair to specify the JNI object
    reference pointer value and what should be done with it.
    The *type* of the JNI object reference is *inferred* by the
    `JniHandleOwnership` value, e.g.
    `JniHandleOwnership.TransferLocalRef` means that the `IntPtr`
    value is a JNI local reference.

  * dotnet/java-interop uses a
    `(ref JniObjectReference, JniObjectReferenceOptions)` pair to
    specify the JNI object reference and what can be done with it.
    The `JniObjectReference` value contains the type, but -- due
    to "pointer trickery"; see 25de1f3 -- it might be *so* invalid
    that it cannot be touched at all, and `JniObjectReferenceOptions`
    will let us know.

Which brings us to the conundrum: how do we implement the
`Java.Lang.Throwable(IntPtr, JniHandleOwnership)` constructor?

	namespace Java.Lang {
	    public class Throwable {
	        public Throwable(IntPtr handle, JniHandleOwnership transfer);
	    }
	}

The "obvious and wrong" solution would be to use `ref` with a
temporary…

	public Throwable(IntPtr handle, JniHandleOwnership transfer)
	    : base (ref new JniObjectReference(handle), …)
	{
	}

…but that won't compile.

Next, we want to be able to provide `message` and `innerException`
parameters to `System.Exception`, but the
`JavaException(ref JniObjectReference, JniObjectReferenceOptions)`
constructor requires a valid JNI Object Reference to do that.

If `Java.Lang.Throwable` tries to "work around" this by using the
`JavaException(string, Exception)` constructor:

	public Throwable(IntPtr handle, JniHandleOwnership transfer)
	    : base (_GetMessage (handle), _GetInnerException (handle))
	{
	    …
	}

then the `JavaException(string, Exception)` constructor will try to
invoke the `E(String)` constructor on the Java side, which:

 1. Is semantically wrong, because we may already have a Java
    instance in `handle`, so why are we creating a new instance? But-

 2. This fails for constructor subclasses which don't provide a
    `E(String)` constructor!  Specifically, the
    [`JnienvTest.ActivatedDirectThrowableSubclassesShouldBeRegistered`][0]
    unit test starts crashing for "bizarre" reasons.

Fix these issues by adding the new exception:

	namespace Java.Interop {
	    partial class JavaException {
		protected JavaException (
	                ref JniObjectReference reference,
	                JniObjectReferenceOptions transfer,
	                JniObjectReference throwableOverride);
	    }
	}

The new `throwableOverride` parameter is used to obtain the `message`
and `innerException` values to provide to the
`System.Exception(string, Exception)` constructor, allowing
`reference` to be a "do not touch" value.

Additionally, add a `protected SetJavaStackTrace()` method which
is used to set the `JavaException.JavaStackTrace` property based on
a `JniObjectReference` value.

[0]: https://github.com/dotnet/android/blob/main/tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs#L285-L305
jonpryor added a commit to dotnet/android that referenced this pull request Jan 15, 2025
Context: xamarin/monodroid@e318861
Context: 130905e
Context: de04316
Context: #9636
Context: dotnet/java-interop#1293

In the beginning there was Mono for Android, which had a set of
`Mono.Android.dll` assemblies (one per supported API level), each of
which contained "duplicated" binding logic: each API level had its
own `Java.Lang.Object`, `Android.Runtime.JNIEnv`, etc.

dotnet/java-interop started, in part, as a way to "split out" the
core integration logic, so that it *wouldn't* need to be duplicated
across every assembly.  As part of this, it introduced its own core
abstractions, notably `Java.Interop.IJavaPeerable` and
`Java.Interop.JavaObject`.

When dotnet/java-interop was first introduced into Xamarin.Android,
with xamarin/monodroid@e318861e, the integration was incomplete.
Integration continued with 130905e, allowing unit tests within
`Java.Interop-Tests.dll` to run within Xamarin.Android and
construction of instances of e.g. `JavaInt32Array`, but one large
piece of integration remained:

Moving GC bridge code *out* of `Java.Lang.Object`, and instead
relying on `Java.Interop.JavaObject`, turning this:

	namespace Java.Lang {
	    public partial class Object : System.Object, IJavaPeerable /* … */ {
	    }
	}

into this:

	namespace Java.Lang {
	    public partial class Object : Java.Interop.JavaObject, IJavaPeerable /* … */ {
	    }
	}

*Why*?  In part because @jonpryor has wanted to do this for literal
years at this point, but also in part because of #9636
and related efforts to use Native AOT, which involves avoiding /
bypassing `DllImportAttribute` invocations (for now, everything
touched by Native AOT becomes a single `.so` binary, which we don't
know the name of).  Avoiding P/Invoke means *embracing* and extending
existing Java.Interop constructs (e.g. de04316).

In addition to altering the base types of `Java.Lang.Object` and
`Java.Lang.Throwable`:

  * Remove `handle` and related fields from `Java.Lang.Object` and
    `Java.Lang.Throwable`.

  * Update `PreserveLists/Mono.Android.xml` so that the removed
    fields are note preserved.

  * Rename `JNIenvInit.AndroidValueManager` to
    `JNIEnvInit.ValueManager`, and change its type to
    `JniRuntime.JniValueManager`.  This is to help "force" usage of
    `JnIRuntime.JniValueManager` in more places, as we can't
    currently use `AndroidValueManager` in Native AOT (P/Invokes!).

  * Cleanup: Remove `JNIEnv.Exit()` and related code.  These were
    used by the Android Designer, which is no longer supported.

  * Update (`internal`) interface `IJavaObjectEx` to remove
    constructs present on `IJavaPeerable.`

  * Update `ExceptionTest.CompareStackTraces()` to use
    `System.Diagnostics.StackTrace(ex, fNeedFileInfo:true)`
    so that when the `debug.mono.debug` system property is set, the
    `ExceptionTest.InnerExceptionIsSet()` unit test doesn't fail.
    Also, increase assertion message verbosity.
@jonpryor
Copy link
Member Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

jonpryor added a commit to dotnet/android that referenced this pull request Jan 15, 2025
Context: xamarin/monodroid@e318861
Context: 130905e
Context: de04316
Context: #9636
Context: dotnet/java-interop#1293

In the beginning there was Mono for Android, which had a set of
`Mono.Android.dll` assemblies (one per supported API level), each of
which contained "duplicated" binding logic: each API level had its
own `Java.Lang.Object`, `Android.Runtime.JNIEnv`, etc.

dotnet/java-interop started, in part, as a way to "split out" the
core integration logic, so that it *wouldn't* need to be duplicated
across every assembly.  As part of this, it introduced its own core
abstractions, notably `Java.Interop.IJavaPeerable` and
`Java.Interop.JavaObject`.

When dotnet/java-interop was first introduced into Xamarin.Android,
with xamarin/monodroid@e318861e, the integration was incomplete.
Integration continued with 130905e, allowing unit tests within
`Java.Interop-Tests.dll` to run within Xamarin.Android and
construction of instances of e.g. `JavaInt32Array`, but one large
piece of integration remained:

Moving GC bridge code *out* of `Java.Lang.Object`, and instead
relying on `Java.Interop.JavaObject`, turning this:

	namespace Java.Lang {
	    public partial class Object : System.Object, IJavaPeerable /* … */ {
	    }
	}

into this:

	namespace Java.Lang {
	    public partial class Object : Java.Interop.JavaObject, IJavaPeerable /* … */ {
	    }
	}

*Why*?  In part because @jonpryor has wanted to do this for literal
years at this point, but also in part because of #9636
and related efforts to use Native AOT, which involves avoiding /
bypassing `DllImportAttribute` invocations (for now, everything
touched by Native AOT becomes a single `.so` binary, which we don't
know the name of).  Avoiding P/Invoke means *embracing* and extending
existing Java.Interop constructs (e.g. de04316).

In addition to altering the base types of `Java.Lang.Object` and
`Java.Lang.Throwable`:

  * Remove `handle` and related fields from `Java.Lang.Object` and
    `Java.Lang.Throwable`.

  * Update `PreserveLists/Mono.Android.xml` so that the removed
    fields are note preserved.

  * Rename `JNIenvInit.AndroidValueManager` to
    `JNIEnvInit.ValueManager`, and change its type to
    `JniRuntime.JniValueManager`.  This is to help "force" usage of
    `JnIRuntime.JniValueManager` in more places, as we can't
    currently use `AndroidValueManager` in Native AOT (P/Invokes!).

  * Cleanup: Remove `JNIEnv.Exit()` and related code.  These were
    used by the Android Designer, which is no longer supported.

  * Update (`internal`) interface `IJavaObjectEx` to remove
    constructs present on `IJavaPeerable.`

  * Update `ExceptionTest.CompareStackTraces()` to use
    `System.Diagnostics.StackTrace(ex, fNeedFileInfo:true)`
    so that when the `debug.mono.debug` system property is set, the
    `ExceptionTest.InnerExceptionIsSet()` unit test doesn't fail.
    Also, increase assertion message verbosity.
@jonpryor
Copy link
Member Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

jonpryor added a commit to dotnet/android that referenced this pull request Jan 15, 2025
Context: xamarin/monodroid@e318861
Context: 130905e
Context: de04316
Context: #9636
Context: dotnet/java-interop#1293

In the beginning there was Mono for Android, which had a set of
`Mono.Android.dll` assemblies (one per supported API level), each of
which contained "duplicated" binding logic: each API level had its
own `Java.Lang.Object`, `Android.Runtime.JNIEnv`, etc.

dotnet/java-interop started, in part, as a way to "split out" the
core integration logic, so that it *wouldn't* need to be duplicated
across every assembly.  As part of this, it introduced its own core
abstractions, notably `Java.Interop.IJavaPeerable` and
`Java.Interop.JavaObject`.

When dotnet/java-interop was first introduced into Xamarin.Android,
with xamarin/monodroid@e318861e, the integration was incomplete.
Integration continued with 130905e, allowing unit tests within
`Java.Interop-Tests.dll` to run within Xamarin.Android and
construction of instances of e.g. `JavaInt32Array`, but one large
piece of integration remained:

Moving GC bridge code *out* of `Java.Lang.Object`, and instead
relying on `Java.Interop.JavaObject`, turning this:

	namespace Java.Lang {
	    public partial class Object : System.Object, IJavaPeerable /* … */ {
	    }
	}

into this:

	namespace Java.Lang {
	    public partial class Object : Java.Interop.JavaObject, IJavaPeerable /* … */ {
	    }
	}

*Why*?  In part because @jonpryor has wanted to do this for literal
years at this point, but also in part because of #9636
and related efforts to use Native AOT, which involves avoiding /
bypassing `DllImportAttribute` invocations (for now, everything
touched by Native AOT becomes a single `.so` binary, which we don't
know the name of).  Avoiding P/Invoke means *embracing* and extending
existing Java.Interop constructs (e.g. de04316).

In addition to altering the base types of `Java.Lang.Object` and
`Java.Lang.Throwable`:

  * Remove `handle` and related fields from `Java.Lang.Object` and
    `Java.Lang.Throwable`.

  * Update `PreserveLists/Mono.Android.xml` so that the removed
    fields are note preserved.

  * Rename `JNIenvInit.AndroidValueManager` to
    `JNIEnvInit.ValueManager`, and change its type to
    `JniRuntime.JniValueManager`.  This is to help "force" usage of
    `JnIRuntime.JniValueManager` in more places, as we can't
    currently use `AndroidValueManager` in Native AOT (P/Invokes!).

  * Cleanup: Remove `JNIEnv.Exit()` and related code.  These were
    used by the Android Designer, which is no longer supported.

  * Update (`internal`) interface `IJavaObjectEx` to remove
    constructs present on `IJavaPeerable.`

  * Update `ExceptionTest.CompareStackTraces()` to use
    `System.Diagnostics.StackTrace(ex, fNeedFileInfo:true)`
    so that when the `debug.mono.debug` system property is set, the
    `ExceptionTest.InnerExceptionIsSet()` unit test doesn't fail.
    Also, increase assertion message verbosity.
Needed to fix `JsonDeserializationCreatesJavaHandle()` test:

    E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.Runtime.Serialization.InvalidDataContractException]: AttributedTypesCannotInheritFromNonAttributedSerializableTypes, Java.Lang.Object, Java.Interop.JavaObject
@jonpryor
Copy link
Member Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@jonpryor
Copy link
Member Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

TODO: why?!
@jonpryor
Copy link
Member Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@jonpryor
Copy link
Member Author

[Java.Interop] `Java.Lang.Object, Mono.Android` Unification Changes

Context: https://github.com/dotnet/android/pull/9640
Context: 25de1f38bb6b3ef2d4c98d2d95923a4bd50d2ea0
Context: 3043d890239cf53eb42633430faa417c140c8952

dotnet/android#9640 completes "unification" with dotnet/java-interop,
updating `Java.Lang.Object` to inherit `Java.Interop.JavaObject`,
just like `src/Java.Base`.

The biggest impediment with this unification step is that the
semantics for referring to a Java instance are quite different:

  * .NET for Android née Xamarin.Android née Mono for Android uses
    an `(IntPtr, JniHandleOwnership)` pair to specify the JNI object
    reference pointer value and what should be done with it.
    The *type* of the JNI object reference is *inferred* by the
    `JniHandleOwnership` value, e.g.
    `JniHandleOwnership.TransferLocalRef` means that the `IntPtr`
    value is a JNI local reference.

  * dotnet/java-interop uses a
    `(ref JniObjectReference, JniObjectReferenceOptions)` pair to
    specify the JNI object reference and what can be done with it.
    The `JniObjectReference` value contains the type, but -- due
    to "pointer trickery"; see 25de1f38 -- it might be *so* invalid
    that it cannot be touched at all, and `JniObjectReferenceOptions`
    will let us know.

Which brings us to the conundrum: how do we implement the
`Java.Lang.Throwable(IntPtr, JniHandleOwnership)` constructor?

	namespace Java.Lang {
	    public class Throwable {
	        public Throwable(IntPtr handle, JniHandleOwnership transfer);
	    }
	}

The "obvious and wrong" solution would be to use `ref` with a
temporary…

	public Throwable(IntPtr handle, JniHandleOwnership transfer)
	    : base (ref new JniObjectReference(handle), …)
	{
	}

…but that won't compile.

Additionally, we want to be able to provide `message` and
`innerException` parameters to `System.Exception`, but the
`JavaException(ref JniObjectReference, JniObjectReferenceOptions)`
constructor requires a valid JNI Object Reference to do that.

If `Java.Lang.Throwable` tries to "work around" this by using the
`JavaException(string, Exception)` constructor:

	public Throwable(IntPtr handle, JniHandleOwnership transfer)
	    : base (_GetMessage (handle), _GetInnerException (handle))
	{
	
	}

then the `JavaException(string, Exception)` constructor will try to
invoke the `E(String)` constructor on the Java side, which:

 1. Is semantically wrong, because we may already have a Java
    instance in `handle`, so why are we creating a new instance? But-

 2. This fails for constructor subclasses which don't provide a
    `E(String)` constructor!  Specifically, the
    [`JnienvTest.ActivatedDirectThrowableSubclassesShouldBeRegistered`][0]
    unit test starts crashing for "bizarre" reasons.

Fix these issues by adding the new `JavaException` constructor:

	namespace Java.Interop {
	    partial class JavaException {
		protected JavaException (
	                ref JniObjectReference reference,
	                JniObjectReferenceOptions transfer,
	                JniObjectReference throwableOverride);
	    }
	}

The new `throwableOverride` parameter is used to obtain the `message`
and `innerException` values to provide to the
`System.Exception(string, Exception)` constructor, allowing
`reference` to be a "do not touch" value.

Additionally, add a `protected SetJavaStackTrace()` method which
is used to set the `JavaException.JavaStackTrace` property based on
a `JniObjectReference` value.

Update `Java.Interop.JavaObject` to be `[Serializable]`, and to mark
its fields as *`[NonSerialized]`*.  This is needed to fix the 
`InstallAndRunTests.JsonDeserializationCreatesJavaHandle()` tests:

	E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.Runtime.Serialization.InvalidDataContractException]: AttributedTypesCannotInheritFromNonAttributedSerializableTypes, Java.Lang.Object, Java.Interop.JavaObject

Finally, update
`Java.Interop.JniRuntime.JniValueManager.ConstructPeer()` to *not*
attempt to call `AddPeer()`/etc. when `peer.JniManagedPeerState`
has `JniManagedPeerStates.Activatable` set.  Failure to do so was
causing `BindingTests.JavaSideActivation()` to fail, as it was
"losing" a GREF.  `JavaSideActivation()` is the scenario described
in 3043d890, in which:

 1. The static Java method `CallMethodFromCtor.NewInstance()` creates
    an instance of `ConstructorTest`, from Java.

 2. The default constructor in the Java Callable Wrapper (JCW) for
    `ConstructorTest` chains to the Java `CallMethodFromCtor()` default
    constructor.

 3. The Java `CallMethodFromCtor()` default constructor invokes the
    method `calledFromCtor()`, which is overridden in the C#
    `ConstructorTest.CalledFromCtor()` method.

 4. This causes the `CallMethodFromCtor.n_CalledFromCtor()` marshal
    method to be invoked, which calls
    `Java.Lang.Object.GetObject<CallMethodFromCtor>()`, which is akin
    to `runtime.ValueManager.GetValue<CallMethodFromCtor>(…)`.

 5. `Object.GetObject<CallMethodFromCtor>()` doesn't find an already
    existing object mapping for the instance we're still trying to
    construct, so it creates a "proxy" object via
    [`TypeManager.CreateInstance()`][2], which winds up invoking the
    `ConstructorTest(IntPtr, JniHandleOwnership)` "activation"
    constructor to create the "proxy".  The activation constructor
    eventually invokes `JavaObject.Construct()` /
    `JniRuntime.JniValueManager.ConstructPeer()`.

    This is the first "important" GREF created.

    Once the proxy is created, it's state is set to
    `JniManagedPeerStates.Activatable`.

 6. The `ConstructorTest.CalledFromCtor()` C# override is invoked,
    control returns to the Java `CallMethodFromCtor` constructor,
    which completes, allowing the JCW `ConstructorTest` constructor
    to execute, which calls [`TypeManager.n_Activate(…)`][3], which
    is equivalent to `ManagedPeer.Construct()`.

 7. `TypeManager.n_Activate(…)` sees that the instance
    "needs activation", and invokes the
    C# `ConstructorTest()` default constructor.  This eventually hits
    (again) `JniRuntime.JniValueManager.ConstructPeer()`.

The problem was that on the second
`JniRuntime.JniValueManager.ConstructPeer()` invocation in (8),
another GREF was created, replacing the GREF created in (5), but
without *disposing* of the GREF created in (5).  This caused a GREF
leak, which the `JavaSideActivation()` test was looking for.

Updating `JniRuntime.JniValueManager.ConstructPeer()` to bail when
it encounters a Peer with `JniManagedPeerStates.Activatable` avoids
the GREF creation in (7), thus avoiding the GREF leak.

[0]: https://github.com/dotnet/android/blob/f9f421b109b2a0f8f805ed14ab1c8dbe651f563e/tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs#L285-L305
[1]: https://github.com/dotnet/android/blob/main/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/BindingTests.cs#L97-L115
[2]: https://github.com/dotnet/android/blob/f9f421b109b2a0f8f805ed14ab1c8dbe651f563e/src/Mono.Android/Java.Interop/TypeManager.cs#L240-L332
[3]: https://github.com/dotnet/android/blob/f9f421b109b2a0f8f805ed14ab1c8dbe651f563e/src/Mono.Android/Java.Interop/TypeManager.cs#L132-L167

Choose a reason for hiding this comment

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

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

Files not reviewed (1)
  • src/Java.Interop/PublicAPI.Unshipped.txt: Language not supported
Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

The [Serializable] changes make sense with what we were seeing in: dotnet/android#9640 (comment)

@jonpryor jonpryor merged commit d5dfa0a into main Jan 16, 2025
4 checks passed
@jonpryor jonpryor deleted the dev/jonp/jonp-overridable-Throwable-ctor-arg branch January 16, 2025 23:04
jonpryor added a commit to dotnet/android that referenced this pull request Jan 17, 2025
Context: #9636
Context: dotnet/java-interop@d5dfa0a
Context: xamarin/monodroid@e318861
Context: 130905e
Context: de04316

Changes: dotnet/java-interop@4f06201...d5dfa0a

  * dotnet/java-interop@d5dfa0aa: [Java.Interop] `Java.Lang.Object, Mono.Android` Unification Changes (dotnet/java-interop#1293)
  * dotnet/java-interop@c86ae26c: [ci] Fail build if any git tracked files were modified. (dotnet/java-interop#1288)

In the beginning there was Mono for Android, which had a set of
`Mono.Android.dll` assemblies (one per supported API level), each of
which contained "duplicated" binding logic: each API level had its
own `Java.Lang.Object`, `Android.Runtime.JNIEnv`, etc.

dotnet/java-interop started, in part, as a way to "split out" the
core integration logic, so that it *wouldn't* need to be duplicated
across every assembly.  As part of this, it introduced its own core
abstractions, notably `Java.Interop.IJavaPeerable` and
`Java.Interop.JavaObject`.

When dotnet/java-interop was first introduced into Xamarin.Android,
with xamarin/monodroid@e318861e, the integration was incomplete.
Integration continued with 130905e, allowing unit tests within
`Java.Interop-Tests.dll` to run within Xamarin.Android and
construction of instances of e.g. `JavaInt32Array`, but one large
piece of integration remained:

Move GC bridge code *out* of `Java.Lang.Object`, and instead rely on
`Java.Interop.JavaObject`, turning this:

	namespace Java.Lang {
	    public partial class Object : System.Object, IJavaPeerable /* … */ {
	    }
	}

into this:

	namespace Java.Lang {
	    public partial class Object : Java.Interop.JavaObject, IJavaPeerable /* … */ {
	    }
	}

*Why*?  In part because @jonpryor has wanted to do this for literal
years at this point, but also in part because of #9636
and related efforts to use Native AOT, which involves avoiding /
bypassing `DllImportAttribute` invocations (for now, everything
touched by Native AOT becomes a single `.so` binary, which we don't
know the name of).  Avoiding P/Invoke means *embracing* and extending
existing Java.Interop constructs (e.g. de04316).

In addition to altering the base types of `Java.Lang.Object` and
`Java.Lang.Throwable`:

  * Remove `handle` and related fields from `Java.Lang.Object` and
    `Java.Lang.Throwable`.

  * Update `PreserveLists/Mono.Android.xml` et al. so that the
    removed fields are not preserved.

  * Rename `JNIenvInit.AndroidValueManager` to
    `JNIEnvInit.ValueManager`, and change its type to
    `JniRuntime.JniValueManager`.  This is to help "force" usage of
    `JnIRuntime.JniValueManager` in more places, as we can't
    currently use `AndroidValueManager` in Native AOT (P/Invokes!).

  * Cleanup: Remove `JNIEnv.Exit()` and related code.  These were
    used by the Android Designer, which is no longer supported.

  * Update (`internal`) interface `IJavaObjectEx` to remove
    constructs present on `IJavaPeerable.`

  * Update the `Java.Lang.Object(IntPtr, JniHandleOwnership)` and
    `Java.Lang.Throwable(IntPtr, JniHandleOwnership)` constructors to
    follow dotnet/java-interop convention, and patch over the
    differences that exist between the two paradigms.

  * Update `ExceptionTest.CompareStackTraces()` to use
    `System.Diagnostics.StackTrace(ex, fNeedFileInfo:true)`
    so that when the `debug.mono.debug` system property is set, the
    `ExceptionTest.InnerExceptionIsSet()` unit test doesn't fail.
    Also, increase assertion message utility.

  * Update `AndroidObjectReferenceManager` so that
    dotnet/java-interop -initiated JNI object reference log messages
    are appropriately captured.

  * Update `JNIEnv.IsGCUserPeer()` to also consider types which
    implement `net.dot.jni.GCUserPeerable` to be "GC User Peers".
@github-actions github-actions bot locked and limited conversation to collaborators Feb 16, 2025
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