-
Notifications
You must be signed in to change notification settings - Fork 534
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
[Mono.Android] Java.Interop Unification! #9640
Draft
jonpryor
wants to merge
3
commits into
main
Choose a base branch
from
dev/jonp/jonp-JavaObject-unification
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
jonpryor
added a commit
to dotnet/java-interop
that referenced
this pull request
Jan 15, 2025
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
force-pushed
the
dev/jonp/jonp-JavaObject-unification
branch
from
January 15, 2025 00:21
4926f0c
to
80177c7
Compare
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
jonpryor
force-pushed
the
dev/jonp/jonp-JavaObject-unification
branch
from
January 15, 2025 01:00
80177c7
to
7ae8702
Compare
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
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
force-pushed
the
dev/jonp/jonp-JavaObject-unification
branch
from
January 15, 2025 01:13
7ae8702
to
881291c
Compare
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
…since we removed that earlier. Fixes warning: D:\a\_work\1\s\bin\Release\lib\packs\Microsoft.Android.Sdk.Windows\35.99.0\targets\..\PreserveLists\Mono.Android.xml(12,5): warning IL2009: Could not find method 'Exit' on type 'Android.Runtime.JNIEnv'. Update `MonodroidRuntime::shutdown_android_runtime()` to abort when called. The Android Designer is not supported.
Fixes `JsonDeserializationCreatesJavaHandle()` test: E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.Runtime.Serialization.InvalidDataContractException]: AttributedTypesCannotInheritFromNonAttributedSerializableTypes, Java.Lang.Object, Java.Interop.JavaObject
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Context: https://github.com/xamarin/monodroid/commit/e318861ed8eb20a71852378ddd558409d6b1c234
Context: 130905e
Context: de04316
Context: #9636
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 ownJava.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
andJava.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 onJava.Interop.JavaObject
, turning this:into this:
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
andJava.Lang.Throwable
:Remove
handle
and related fields fromJava.Lang.Object
andJava.Lang.Throwable
.Update
PreserveLists/Mono.Android.xml
so that the removed fields are note preserved.Rename
JNIenvInit.AndroidValueManager
toJNIEnvInit.ValueManager
, and change its type toJniRuntime.JniValueManager
. This is to help "force" usage ofJnIRuntime.JniValueManager
in more places, as we can't currently useAndroidValueManager
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
) interfaceIJavaObjectEx
to remove constructs present onIJavaPeerable.
Known issues:
Java.Lang.Throwable(IntPtr, JniHandleOwnership)
invocation will result in construction of an "extra"java.lang.Throwable
instance on the Java side, which will be immediately discarded. This is because it uses theJavaException(string, Exception)
constructor, which implicitly creates a Java peer.We may need dotnet/java-interop changes to better address this.