Skip to content

Commit 5c23bcd

Browse files
authored
[Mono.Android] Java.Interop Unification! (#9640)
Context: #9636 Context: dotnet/java-interop@d5dfa0a Context: https://github.com/xamarin/monodroid/commit/e318861ed8eb20a71852378ddd558409d6b1c234 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".
1 parent f9f421b commit 5c23bcd

19 files changed

Lines changed: 123 additions & 625 deletions

File tree

‎src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
<typefullname="Android.Runtime.InputStreamAdapter"preserve="methods" />
1010
<typefullname="Android.Runtime.InputStreamInvoker"preserve="methods" />
1111
<typefullname="Android.Runtime.JNIEnv">
12-
<methodname="Exit" />
1312
<methodname="PropagateUncaughtException" />
1413
</type>
1514
<typefullname="Android.Runtime.JNIEnvInit">
@@ -36,15 +35,7 @@
3635
-->
3736
<typefullname="Java.Interop.TypeManager*JavaTypeManager" />
3837
<typefullname="Java.Lang.Object">
39-
<fieldname="handle" />
40-
<fieldname="handle_type" />
41-
<fieldname="refs_added" />
4238
<methodname="SetHandleOnDeserialized" />
4339
</type>
44-
<typefullname="Java.Lang.Throwable">
45-
<fieldname="handle" />
46-
<fieldname="handle_type" />
47-
<fieldname="refs_added" />
48-
</type>
4940
</assembly>
5041
</linker>

‎src/Mono.Android/Android.Runtime/AndroidRuntime.cs‎

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,29 +56,36 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f
5656
{
5757
if(!reference.IsValid)
5858
returnnull;
59-
varpeeked=JNIEnvInit.AndroidValueManager?.PeekPeer(reference);
59+
varpeeked=JNIEnvInit.ValueManager?.PeekPeer(reference);
6060
varpeekedExc=peekedasException;
6161
if(peekedExc==null){
6262
varthrowable=Java.Lang.Object.GetObject<Java.Lang.Throwable>(reference.Handle,JniHandleOwnership.DoNotTransfer);
6363
JniObjectReference.Dispose(refreference,options);
6464
returnthrowable;
6565
}
6666
JniObjectReference.Dispose(refreference,options);
67-
varunwrapped=JNIEnvInit.AndroidValueManager?.UnboxException(peeked!);
67+
varunwrapped=UnboxException(peeked!);
6868
if(unwrapped!=null){
6969
returnunwrapped;
7070
}
7171
returnpeekedExc;
7272
}
7373

74+
Exception?UnboxException(IJavaPeerablevalue)
75+
{
76+
if(JNIEnvInit.ValueManagerisAndroidValueManagervm){
77+
returnvm.UnboxException(value);
78+
}
79+
returnnull;
80+
}
81+
7482
publicoverridevoidRaisePendingException(ExceptionpendingException)
7583
{
76-
varje=pendingExceptionasJavaProxyThrowable;
84+
varje=pendingExceptionasJavaException;
7785
if(je==null){
7886
je=JavaProxyThrowable.Create(pendingException);
7987
}
80-
varr=newJniObjectReference(je.Handle);
81-
JniEnvironment.Exceptions.Throw(r);
88+
JniEnvironment.Exceptions.Throw(je.PeerReference);
8289
}
8390
}
8491

@@ -158,6 +165,14 @@ public override IntPtr ReleaseLocalReference (ref JniObjectReference value, ref
158165
returnr;
159166
}
160167

168+
publicoverrideboolLogGlobalReferenceMessages=>Logger.LogGlobalRef;
169+
publicoverrideboolLogLocalReferenceMessages=>Logger.LogLocalRef;
170+
171+
publicoverridevoidWriteLocalReferenceLine(stringformat,paramsobject?[]args)
172+
{
173+
RuntimeNativeMethods._monodroid_gref_log("[LREF] "+string.Format(CultureInfo.InvariantCulture,format,args));
174+
}
175+
161176
publicoverridevoidWriteGlobalReferenceLine(stringformat,paramsobject?[]args)
162177
{
163178
RuntimeNativeMethods._monodroid_gref_log(string.Format(CultureInfo.InvariantCulture,format,args));
@@ -470,6 +485,7 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu
470485
}
471486
}
472487

488+
[Obsolete("Use RegisterNativeMembers(JniType, Type, ReadOnlySpan<char>) instead.")]
473489
publicoverridevoidRegisterNativeMembers(
474490
JniTypenativeClass,
475491
[DynamicallyAccessedMembers(MethodsAndPrivateNested)]

‎src/Mono.Android/Android.Runtime/JNIEnv.cs‎

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ internal static bool IsGCUserPeer (IntPtr value)
5252
if(value==IntPtr.Zero)
5353
returnfalse;
5454

55-
returnIsInstanceOf(value,JNIEnvInit.grefIGCUserPeer_class);
55+
returnIsInstanceOf(value,JNIEnvInit.grefIGCUserPeer_class)||
56+
IsInstanceOf(value,JNIEnvInit.grefGCUserPeerable_class);
5657
}
5758

5859
internalstaticboolShouldWrapJavaException(Java.Lang.Throwable?t,[CallerMemberName]string?caller=null)
@@ -76,49 +77,6 @@ internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMem
7677
returnwrap;
7778
}
7879

79-
[DllImport("libc")]
80-
staticexternintgettid();
81-
82-
internalstaticvoidExit()
83-
{
84-
/* Manually dispose surfaced objects and close the current JniEnvironment to
85-
* avoid ObjectDisposedException thrown on finalizer threads after shutdown
86-
*/
87-
foreach(varsurfacedObjectinJava.Interop.Runtime.GetSurfacedObjects()){
88-
try{
89-
varobj=surfacedObject.TargetasIDisposable;
90-
if(obj!=null)
91-
obj.Dispose();
92-
continue;
93-
}catch(Exceptione){
94-
RuntimeNativeMethods.monodroid_log(LogLevel.Warn,LogCategories.Default,$"Couldn't dispose object: {e}");
95-
}
96-
/* If calling Dispose failed, the assumption is that user-code in
97-
* the Dispose(bool) overload is to blame for it. In that case we
98-
* fallback to manual deletion of the surfaced object.
99-
*/
100-
varjobj=surfacedObject.TargetasJava.Lang.Object;
101-
if(jobj!=null)
102-
ManualJavaObjectDispose(jobj);
103-
}
104-
JniEnvironment.Runtime.Dispose();
105-
}
106-
107-
/* FIXME: This reproduces the minimal steps in Java.Lang.Object.Dispose
108-
* that needs to be executed so that we don't leak any GREF and prevent
109-
* code execution into an appdomain that we are disposing via a finalizer.
110-
* Ideally it should be done via another more generic mechanism, likely
111-
* from the Java.Interop.Runtime API.
112-
*/
113-
staticvoidManualJavaObjectDispose(Java.Lang.Objectobj)
114-
{
115-
varpeer=obj.PeerReference;
116-
varhandle=peer.Handle;
117-
varkeyHandle=((IJavaObjectEx)obj).KeyHandle;
118-
Java.Lang.Object.Dispose(obj,refhandle,keyHandle,(JObjectRefType)peer.Type);
119-
GC.SuppressFinalize(obj);
120-
}
121-
12280
internalstaticvoidPropagateUncaughtException(IntPtrenv,IntPtrjavaThreadPtr,IntPtrjavaExceptionPtr)
12381
{
12482
if(!JNIEnvInit.PropagateExceptions)

‎src/Mono.Android/Android.Runtime/JNIEnvInit.cs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,19 @@ internal struct JnienvInitializeArgs {
3232
publicintjniAddNativeMethodRegistrationAttributePresent;
3333
publicbooljniRemappingInUse;
3434
publicboolmarshalMethodsEnabled;
35+
publicIntPtrgrefGCUserPeerable;
3536
}
3637
#pragma warning restore 0649
3738

38-
internalstaticAndroidValueManager?AndroidValueManager;
39+
internalstaticJniRuntime.JniValueManager?ValueManager;
3940
internalstaticboolIsRunningOnDesktop;
4041
internalstaticbooljniRemappingInUse;
4142
internalstaticboolMarshalMethodsEnabled;
4243
internalstaticboolPropagateExceptions;
4344
internalstaticBoundExceptionTypeBoundExceptionType;
4445
internalstaticintgref_gc_threshold;
4546
internalstaticIntPtrgrefIGCUserPeer_class;
47+
internalstaticIntPtrgrefGCUserPeerable_class;
4648
internalstaticIntPtrjava_class_loader;
4749
internalstaticJniMethodInfo?mid_Class_forName;
4850

@@ -94,11 +96,12 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
9496

9597
BoundExceptionType=(BoundExceptionType)args->ioExceptionType;
9698
androidRuntime=newAndroidRuntime(args->env,args->javaVm,args->grefLoader,args->Loader_loadClass,args->jniAddNativeMethodRegistrationAttributePresent!=0);
97-
AndroidValueManager=(AndroidValueManager)androidRuntime.ValueManager;
99+
ValueManager=androidRuntime.ValueManager;
98100

99101
IsRunningOnDesktop=args->isRunningOnDesktop==1;
100102

101103
grefIGCUserPeer_class=args->grefIGCUserPeer;
104+
grefGCUserPeerable_class=args->grefGCUserPeerable;
102105

103106
PropagateExceptions=args->brokenExceptionTransitions==0;
104107

‎src/Mono.Android/Java.Interop/IJavaObjectEx.cs‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
namespaceJava.Interop{
44

55
interfaceIJavaObjectEx{
6-
IntPtrKeyHandle{get;set;}
7-
boolIsProxy{get;set;}
8-
boolNeedsActivation{get;set;}
96
IntPtrToLocalJniHandle();
107
}
118
}

‎src/Mono.Android/Java.Interop/Runtime.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public static class Runtime {
1111
[Obsolete("Please use Java.Interop.JniEnvironment.Runtime.ValueManager.GetSurfacedPeers()")]
1212
publicstaticList<WeakReference>GetSurfacedObjects()
1313
{
14-
varpeers=JNIEnvInit.AndroidValueManager!.GetSurfacedPeers();
14+
varpeers=JNIEnvInit.ValueManager!.GetSurfacedPeers();
1515
varr=newList<WeakReference>(peers.Count);
1616
foreach(varpinpeers){
1717
if(p.SurfacedPeer.TryGetTarget(outvartarget))

‎src/Mono.Android/Java.Interop/TypeManager.cs‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,10 @@ static Type[] GetParameterTypes (string? signature)
133133
staticvoidn_Activate(IntPtrjnienv,IntPtrjclass,IntPtrtypename_ptr,IntPtrsignature_ptr,IntPtrjobject,IntPtrparameters_ptr)
134134
{
135135
varo=Java.Lang.Object.PeekObject(jobject);
136-
varex=oasIJavaObjectEx;
136+
varex=oasIJavaPeerable;
137137
if(ex!=null){
138-
if(!ex.NeedsActivation&&!ex.IsProxy)
138+
varstate=ex.JniManagedPeerState;
139+
if(!state.HasFlag(JniManagedPeerStates.Activatable)&&!state.HasFlag(JniManagedPeerStates.Replaceable))
139140
return;
140141
}
141142
if(!ActivationEnabled){
@@ -171,10 +172,8 @@ internal static void Activate (IntPtr jobject, ConstructorInfo cinfo, object? []
171172
{
172173
try{
173174
varnewobj=RuntimeHelpers.GetUninitializedObject(cinfo.DeclaringType!);
174-
if(newobjisJava.Lang.Objecto){
175-
o.handle=jobject;
176-
}elseif(newobjisJava.Lang.Throwablethrowable){
177-
throwable.handle=jobject;
175+
if(newobjisIJavaPeerablepeer){
176+
peer.SetPeerReference(newJniObjectReference(jobject));
178177
}else{
179178
thrownewInvalidOperationException($"Unsupported type: '{newobj}'");
180179
}
@@ -232,7 +231,7 @@ static Exception CreateJavaLocationException ()
232231
returnnull;
233232
}
234233

235-
internalstaticIJavaPeerableCreateInstance(IntPtrhandle,JniHandleOwnershiptransfer)
234+
internalstaticIJavaPeerable?CreateInstance(IntPtrhandle,JniHandleOwnershiptransfer)
236235
{
237236
returnCreateInstance(handle,transfer,null);
238237
}
@@ -320,7 +319,7 @@ internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership
320319
try{
321320
result=(IJavaPeerable)CreateProxy(type,handle,transfer);
322321
if(Runtime.IsGCUserPeer(result.PeerReference.Handle)){
323-
result.SetJniManagedPeerState(JniManagedPeerStates.Replaceable);
322+
result.SetJniManagedPeerState(JniManagedPeerStates.Replaceable|JniManagedPeerStates.Activatable);
324323
}
325324
}catch(MissingMethodExceptione){
326325
varkey_handle=JNIEnv.IdentityHash(handle);
@@ -353,7 +352,6 @@ internal static object CreateProxy (
353352
JniObjectReferenceOptionso=JniObjectReferenceOptions.Copy;
354353
varpeer=(IJavaPeerable)c.Invoke(newobject[]{r,o});
355354
JNIEnv.DeleteRef(handle,transfer);
356-
peer.SetJniManagedPeerState(peer.JniManagedPeerState|JniManagedPeerStates.Replaceable);
357355
returnpeer;
358356
}
359357
thrownewMissingMethodException(

0 commit comments

Comments
 (0)