Skip to content

Commit dd3c1d0

Browse files
authored
[Java.Interop] Add JniRuntime.JniValueManager.TryCreatePeer() (#1301)
Context: 78d5937 Context: be6cc8f 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: [Application (Name = "my.MainApplication")] public class MainApplication : Application { public MainApplication (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } public override void OnCreate () { base.OnCreate (); } } 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 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. Additionally, add `JniRuntime.JniTypeManager.GetInvokerType()`: partial class JniRuntime { partial class JniTypeManager { public Type? GetInvokerType (Type type); protected virtual Type? GetInvokerTypeCore (Type type); } } `GetInvokerType()` calls `GetInvokerTypeCore()`, allowing flexibility for subclasses to lookup the "string-based" the .NET for Android -style Invoker types that JavaInterop1-style bindings used before commit be6cc8f.
1 parent fb642c9 commit dd3c1d0

File tree

5 files changed

+146
-57
lines changed

5 files changed

+146
-57
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0"?>
2+
<docs>
3+
<member name="T:JniTypeManager">
4+
<summary>
5+
Manages bound Java types.
6+
</summary>
7+
<remarks>
8+
</remarks>
9+
</member>
10+
<member name="M:GetInvokerType">
11+
<summary>Gets the <i>Invoker</i> type for <paramref name="type" /></summary>
12+
<remarks>
13+
<para>
14+
An <i>Invoker type</i> is a concrete type which can be constructed,
15+
which is used to invoke instances of abstract type that cannot be constructed.
16+
For example, the interface type <c>Java.Lang.IRunnable</c> cannot be constructed,
17+
but if a <c>java.lang.Runnable</c> instance enters managed code,
18+
a Invoker must be constructed around the instance so that it may be used.
19+
</para>
20+
</remarks>
21+
<returns>
22+
If <paramref name="type" /> is an interface or abstract class, returns the
23+
type which should be constructed around instances of <paramref name="type" />.
24+
If no such type exists, or if <paramref name="type" /> is a concrete type,
25+
then <see langword="null" /> is returned.
26+
</returns>
27+
</member>
28+
</docs>

src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs

+45
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,10 @@ public override string ToString ()
7979
}
8080
#endif // NET
8181

82+
/// <include file="../Documentation/Java.Interop/JniRuntime.JniTypeManager.xml" path="/docs/member[@name='T:JniTypeManager']/*" />
8283
public partial class JniTypeManager : IDisposable, ISetRuntime {
8384

85+
internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
8486
internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods;
8587
internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;
8688
internal const DynamicallyAccessedMemberTypes MethodsConstructors = MethodsAndPrivateNested | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
@@ -385,6 +387,49 @@ IEnumerable<Type> CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe
385387
yield break;
386388
}
387389

390+
/// <include file="../Documentation/Java.Interop/JniRuntime.JniTypeManager.xml" path="/docs/member[@name='M:GetInvokerType']/*" />
391+
[return: DynamicallyAccessedMembers (Constructors)]
392+
public Type? GetInvokerType (
393+
[DynamicallyAccessedMembers (Constructors)]
394+
Type type)
395+
{
396+
if (type.IsAbstract || type.IsInterface) {
397+
return GetInvokerTypeCore (type);
398+
}
399+
return null;
400+
}
401+
402+
[return: DynamicallyAccessedMembers (Constructors)]
403+
protected virtual Type? GetInvokerTypeCore (
404+
[DynamicallyAccessedMembers (Constructors)]
405+
Type type)
406+
{
407+
// https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186
408+
const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step.";
409+
410+
[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)]
411+
[return: DynamicallyAccessedMembers (Constructors)]
412+
static Type MakeGenericType (
413+
[DynamicallyAccessedMembers (Constructors)]
414+
Type type,
415+
Type [] arguments) =>
416+
// FIXME: https://github.com/dotnet/java-interop/issues/1192
417+
#pragma warning disable IL3050
418+
type.MakeGenericType (arguments);
419+
#pragma warning restore IL3050
420+
421+
var signature = type.GetCustomAttribute<JniTypeSignatureAttribute> ();
422+
if (signature == null || signature.InvokerType == null) {
423+
return null;
424+
}
425+
426+
Type[] arguments = type.GetGenericArguments ();
427+
if (arguments.Length == 0)
428+
return signature.InvokerType;
429+
430+
return MakeGenericType (signature.InvokerType, arguments);
431+
}
432+
388433
#if NET
389434

390435
public IReadOnlyList<string>? GetStaticMethodFallbackTypes (string jniSimpleReference)

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

+37-57
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,11 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
373363
type = Runtime.TypeManager.GetType (sig);
374364

375365
if (type != null) {
376-
var ctor = GetActivationConstructor (type);
366+
var peer = TryCreatePeerInstance (ref reference, transfer, type);
377367

378-
if (ctor != null) {
368+
if (peer != null) {
379369
JniObjectReference.Dispose (ref klass);
380-
return ctor;
370+
return peer;
381371
}
382372
}
383373

@@ -391,51 +381,41 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
391381
}
392382
JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose);
393383

394-
return GetActivationConstructor (fallbackType);
384+
return TryCreatePeerInstance (ref reference, transfer, fallbackType);
395385
}
396386

397-
static ConstructorInfo? GetActivationConstructor (
387+
IJavaPeerable? TryCreatePeerInstance (
388+
ref JniObjectReference reference,
389+
JniObjectReferenceOptions options,
398390
[DynamicallyAccessedMembers (Constructors)]
399391
Type type)
400392
{
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;
408-
}
409-
return null;
393+
type = Runtime.TypeManager.GetInvokerType (type) ?? type;
394+
return TryCreatePeer (ref reference, options, type);
410395
}
411396

412-
[return: DynamicallyAccessedMembers (Constructors)]
413-
static Type? GetInvokerType (Type type)
414-
{
415-
// https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186
416-
const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step.";
417-
418-
[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)]
419-
[return: DynamicallyAccessedMembers (Constructors)]
420-
static Type MakeGenericType (
421-
[DynamicallyAccessedMembers (Constructors)]
422-
Type type,
423-
Type [] arguments) =>
424-
// FIXME: https://github.com/dotnet/java-interop/issues/1192
425-
#pragma warning disable IL3050
426-
type.MakeGenericType (arguments);
427-
#pragma warning restore IL3050
428-
429-
var signature = type.GetCustomAttribute<JniTypeSignatureAttribute> ();
430-
if (signature == null || signature.InvokerType == null) {
431-
return null;
432-
}
397+
const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
398+
static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType ();
399+
static readonly Type[] JIConstructorSignature = new Type [] { ByRefJniObjectReference, typeof (JniObjectReferenceOptions) };
433400

434-
Type[] arguments = type.GetGenericArguments ();
435-
if (arguments.Length == 0)
436-
return signature.InvokerType;
437401

438-
return MakeGenericType (signature.InvokerType, arguments);
402+
protected virtual IJavaPeerable? TryCreatePeer (
403+
ref JniObjectReference reference,
404+
JniObjectReferenceOptions options,
405+
[DynamicallyAccessedMembers (Constructors)]
406+
Type type)
407+
{
408+
var c = type.GetConstructor (ActivationConstructorBindingFlags, null, JIConstructorSignature, null);
409+
if (c != null) {
410+
var args = new object[] {
411+
reference,
412+
options,
413+
};
414+
var p = (IJavaPeerable) c.Invoke (args);
415+
reference = (JniObjectReference) args [0];
416+
return p;
417+
}
418+
return null;
439419
}
440420

441421
public object? CreateValue (

src/Java.Interop/PublicAPI.Unshipped.txt

+3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ 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.JniTypeManager.GetInvokerTypeCore(System.Type! type) -> System.Type?
7+
virtual Java.Interop.JniRuntime.JniValueManager.TryCreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> Java.Interop.IJavaPeerable?
68
Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void
79
Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void
10+
Java.Interop.JniRuntime.JniTypeManager.GetInvokerType(System.Type! type) -> System.Type?
811
Java.Interop.JniRuntime.JniValueManager.GetPeer(Java.Interop.JniObjectReference reference, System.Type? targetType = null) -> Java.Interop.IJavaPeerable?
912
Java.Interop.JniTypeSignatureAttribute.InvokerType.get -> System.Type?
1013
Java.Interop.JniTypeSignatureAttribute.InvokerType.set -> void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Collections.Generic;
4+
5+
using Java.Interop;
6+
7+
using NUnit.Framework;
8+
9+
namespace Java.InteropTests {
10+
11+
[TestFixture]
12+
public class JniRuntimeJniTypeManagerTests : JavaVMFixture {
13+
14+
[Test]
15+
public void GetInvokerType ()
16+
{
17+
using (var vm = new MyTypeManager ()) {
18+
// Concrete type; no invoker
19+
Assert.IsNull (vm.GetInvokerType (typeof (JavaObject)));
20+
21+
// Not a bound abstract Java type; no invoker
22+
Assert.IsNull (vm.GetInvokerType (typeof (System.ICloneable)));
23+
24+
// Bound abstract Java type; has an invoker
25+
Assert.AreSame (typeof (IJavaInterfaceInvoker), vm.GetInvokerType (typeof (IJavaInterface)));
26+
}
27+
}
28+
29+
class MyTypeManager : JniRuntime.JniTypeManager {
30+
}
31+
}
32+
}
33+

0 commit comments

Comments
 (0)