Skip to content

Commit ae9346a

Browse files
committed
[Java.Interop] Add JniRuntime.JniValueManager.TryCreatePeer()
Context: 78d5937 Context: dotnet/android@7a772f0 Context: dotnet/android#9716 Context: dotnet/android@694e975 dotnet/android@7a772f03 added the beginnings of a NativeAOT sample to dotnet/android which built a ".NET for Android" app using NativeAOT, which "rhymed with" the `Hello-NativeAOTFromAndroid` sample in 78d5937. Further work on the sample showed that it was lacking support for `Android.App.Application` subclasses. dotnet/android#9716 began fixing that oversight, but in the process was triggering a stack overflow because when it needed to create a "proxy" peer around the `my.MainApplication` Java type, which subclassed `android.app.Application`, instead of creating an instance of the expected `MainApplication` C# type, it instead created an instance of `Android.App.Application`. This was visible from the logs: Created PeerReference=0x2d06/G IdentityHashCode=0x8edcb07 Instance=0x957d2a Instance.Type=Android.App.Application, Java.Type=my/MainApplication Note that `Instance.Type` is `Android.App.Application`, not the in-sample `MainApplication` C# type. Because the runtime type was `Android.App.Application`, when we later attempted to dispatch the `Application.OnCreate()` override, this resulted in a *virtual* invocation of the Java `Application.onCreate()` method instead of a *non-virtual* invocation of `Application.onCreate()`. This virtual invocation was the root of a recursive loop which eventually resulted in a stack overflow. The fix in dotnet/android@694e975e was to fix `NativeAotTypeManager.CreatePeer()` so that it properly checked for a binding of the *runtime type* of the Java instance *before* using the "fallback" type provided to `Object.GetObject<T>()` in the `Application.n_OnCreate()` method: partial class Application { static void n_OnCreate (IntPtr jnienv, IntPtr native__this) { // … var __this = global::Java.Lang.Object.GetObject< Android.App.Application // This is the "fallback" NativeAotTypeManager > (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; __this.OnCreate (); // … } } All well and good. The problem is that `NativeAotTypeManager` in dotnet/android needs to support *both* dotnet/java-interop "activation constructors" with a signature of `(ref JniObjectReference, JniObjectReferenceOptions)`, *and* the .NET for Android signature of `(IntPtr, JniHandleOwnership)`. Trying to support both constructors resulted in the need to copy *all* of `JniRuntime.JniValueManager.CreatePeer()` *and dependencies*, which felt a bit excessive. Add a new `JniRuntime.JniValueManager.TryCreatePeer()` method, which will invoke the activation constructor to create an `IJavaPeerable`: partial class JniRuntime { partial class JniValueManager { protected virtual IJavaPeerable? TryCreatePeer ( ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType); } } If the activation constructor is not found, then `TryCreatePeer()` shall return `null`, allowing `CreatePeerInstance()` to try for a base type or, ultimately, the fallback type. This will allow a future dotnet/android PR to *remove* `NativeAotTypeManager.CreatePeer()` and its dependencies entirely, and instead do: partial class NativeAotTypeManager { const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; protected override IJavaPeerable TryCreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type type) { var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null); if (c != null) { var args = new object[] { reference.Handle, JniHandleOwnership.DoNotTransfer, }; var p = (IJavaPeerable) c.Invoke (args); JniObjectReference.Dispose (ref reference, options); return p; } return base.TryCreatePeer (ref reference, options, type); } } vastly reducing the code it needs to care about.
1 parent e288589 commit ae9346a

File tree

2 files changed

+31
-30
lines changed

2 files changed

+31
-30
lines changed

src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -336,31 +336,21 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
336336

337337
JniObjectReference.Dispose (ref targetClass);
338338

339-
var ctor = GetPeerConstructor (ref refClass, targetType);
340-
if (ctor == null) {
339+
var peer = CreatePeerInstance (ref refClass, targetType, ref reference, transfer);
340+
if (peer == null) {
341341
throw new NotSupportedException (string.Format ("Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.",
342342
JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType));
343343
}
344-
345-
var acts = new object[] {
346-
reference,
347-
transfer,
348-
};
349-
try {
350-
var peer = (IJavaPeerable) ctor.Invoke (acts);
351-
peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable);
352-
return peer;
353-
} finally {
354-
reference = (JniObjectReference) acts [0];
355-
}
344+
peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable);
345+
return peer;
356346
}
357347

358-
static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType ();
359-
360-
ConstructorInfo? GetPeerConstructor (
348+
IJavaPeerable? CreatePeerInstance (
361349
ref JniObjectReference klass,
362350
[DynamicallyAccessedMembers (Constructors)]
363-
Type fallbackType)
351+
Type fallbackType,
352+
ref JniObjectReference reference,
353+
JniObjectReferenceOptions transfer)
364354
{
365355
var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass);
366356

@@ -373,11 +363,12 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
373363
type = Runtime.TypeManager.GetType (sig);
374364

375365
if (type != null) {
376-
var ctor = GetActivationConstructor (type);
366+
type = GetInvokerType (type) ?? type;
367+
var peer = TryCreatePeer (ref reference, transfer, type);
377368

378-
if (ctor != null) {
369+
if (peer != null) {
379370
JniObjectReference.Dispose (ref klass);
380-
return ctor;
371+
return peer;
381372
}
382373
}
383374

@@ -391,20 +382,29 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
391382
}
392383
JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose);
393384

394-
return GetActivationConstructor (fallbackType);
385+
return TryCreatePeer (ref reference, transfer, fallbackType);
395386
}
396387

397-
static ConstructorInfo? GetActivationConstructor (
388+
const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
389+
static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType ();
390+
static readonly Type[] JIConstructorSignature = new Type [] { ByRefJniObjectReference, typeof (JniObjectReferenceOptions) };
391+
392+
393+
protected virtual IJavaPeerable? TryCreatePeer (
394+
ref JniObjectReference reference,
395+
JniObjectReferenceOptions options,
398396
[DynamicallyAccessedMembers (Constructors)]
399397
Type type)
400398
{
401-
if (type.IsAbstract || type.IsInterface) {
402-
type = GetInvokerType (type) ?? type;
403-
}
404-
foreach (var c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
405-
var p = c.GetParameters ();
406-
if (p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions))
407-
return c;
399+
var c = type.GetConstructor (ActivationConstructorBindingFlags, null, JIConstructorSignature, null);
400+
if (c != null) {
401+
var args = new object[] {
402+
reference,
403+
options,
404+
};
405+
var p = (IJavaPeerable) c.Invoke (args);
406+
reference = (JniObjectReference) args [0];
407+
return p;
408408
}
409409
return null;
410410
}

src/Java.Interop/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ static Java.Interop.JniEnvironment.BeginMarshalMethod(nint jnienv, out Java.Inte
33
static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransition transition) -> void
44
virtual Java.Interop.JniRuntime.OnEnterMarshalMethod() -> void
55
virtual Java.Interop.JniRuntime.OnUserUnhandledException(ref Java.Interop.JniTransition transition, System.Exception! e) -> void
6+
virtual Java.Interop.JniRuntime.JniValueManager.TryCreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> Java.Interop.IJavaPeerable?
67
Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void
78
Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void
89
Java.Interop.JniRuntime.JniValueManager.GetPeer(Java.Interop.JniObjectReference reference, System.Type? targetType = null) -> Java.Interop.IJavaPeerable?

0 commit comments

Comments
 (0)