From ae9346a253efa108a0d81434bc61a2f0e35489d9 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 31 Jan 2025 18:22:50 -0500 Subject: [PATCH 1/7] [Java.Interop] Add `JniRuntime.JniValueManager.TryCreatePeer()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: 78d59371a9e27aa601bc32a07a529d09e2f7c796 Context: https://github.com/dotnet/android/commit/7a772f03fc7e5d9330d89a976c5e8e0b655e1588 Context: https://github.com/dotnet/android/pull/9716 Context: https://github.com/dotnet/android/pull/9716/commits/694e975ec4ef2fbe32cde842fcc38f8e7583261f 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 78d59371. 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()` 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. --- .../JniRuntime.JniValueManager.cs | 60 +++++++++---------- src/Java.Interop/PublicAPI.Unshipped.txt | 1 + 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index ad8d04c35..3f10d652f 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -336,31 +336,21 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) JniObjectReference.Dispose (ref targetClass); - var ctor = GetPeerConstructor (ref refClass, targetType); - if (ctor == null) { + var peer = CreatePeerInstance (ref refClass, targetType, ref reference, transfer); + if (peer == null) { throw new NotSupportedException (string.Format ("Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.", JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType)); } - - var acts = new object[] { - reference, - transfer, - }; - try { - var peer = (IJavaPeerable) ctor.Invoke (acts); - peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); - return peer; - } finally { - reference = (JniObjectReference) acts [0]; - } + peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); + return peer; } - static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType (); - - ConstructorInfo? GetPeerConstructor ( + IJavaPeerable? CreatePeerInstance ( ref JniObjectReference klass, [DynamicallyAccessedMembers (Constructors)] - Type fallbackType) + Type fallbackType, + ref JniObjectReference reference, + JniObjectReferenceOptions transfer) { var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass); @@ -373,11 +363,12 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) type = Runtime.TypeManager.GetType (sig); if (type != null) { - var ctor = GetActivationConstructor (type); + type = GetInvokerType (type) ?? type; + var peer = TryCreatePeer (ref reference, transfer, type); - if (ctor != null) { + if (peer != null) { JniObjectReference.Dispose (ref klass); - return ctor; + return peer; } } @@ -391,20 +382,29 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) } JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); - return GetActivationConstructor (fallbackType); + return TryCreatePeer (ref reference, transfer, fallbackType); } - static ConstructorInfo? GetActivationConstructor ( + const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType (); + static readonly Type[] JIConstructorSignature = new Type [] { ByRefJniObjectReference, typeof (JniObjectReferenceOptions) }; + + + protected virtual IJavaPeerable? TryCreatePeer ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, [DynamicallyAccessedMembers (Constructors)] Type type) { - if (type.IsAbstract || type.IsInterface) { - type = GetInvokerType (type) ?? type; - } - foreach (var c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - var p = c.GetParameters (); - if (p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions)) - return c; + var c = type.GetConstructor (ActivationConstructorBindingFlags, null, JIConstructorSignature, null); + if (c != null) { + var args = new object[] { + reference, + options, + }; + var p = (IJavaPeerable) c.Invoke (args); + reference = (JniObjectReference) args [0]; + return p; } return null; } diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index 5a788dfa6..d0b8563b2 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -3,6 +3,7 @@ static Java.Interop.JniEnvironment.BeginMarshalMethod(nint jnienv, out Java.Inte static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransition transition) -> void virtual Java.Interop.JniRuntime.OnEnterMarshalMethod() -> void virtual Java.Interop.JniRuntime.OnUserUnhandledException(ref Java.Interop.JniTransition transition, System.Exception! e) -> void +virtual Java.Interop.JniRuntime.JniValueManager.TryCreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> Java.Interop.IJavaPeerable? Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void Java.Interop.JniRuntime.JniValueManager.GetPeer(Java.Interop.JniObjectReference reference, System.Type? targetType = null) -> Java.Interop.IJavaPeerable? From c4765d04561cc6eb88bc6ede3c47b492189c8138 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Sat, 1 Feb 2025 11:09:22 -0500 Subject: [PATCH 2/7] Look for Invoker types in the right places. Fixes unit test failures. --- .../Java.Interop/JniRuntime.JniValueManager.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 3f10d652f..6c3d7c11a 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -363,8 +363,7 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) type = Runtime.TypeManager.GetType (sig); if (type != null) { - type = GetInvokerType (type) ?? type; - var peer = TryCreatePeer (ref reference, transfer, type); + var peer = TryCreatePeerInstance (ref reference, transfer, type); if (peer != null) { JniObjectReference.Dispose (ref klass); @@ -382,7 +381,19 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) } JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); - return TryCreatePeer (ref reference, transfer, fallbackType); + return TryCreatePeerInstance (ref reference, transfer, fallbackType); + } + + IJavaPeerable? TryCreatePeerInstance ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + if (type.IsAbstract || type.IsInterface) { + type = GetInvokerType (type) ?? type; + } + return TryCreatePeer (ref reference, options, type); } const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; From 41c5bc7f6f569a8e6e2b32c1669247fd417608b9 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Sat, 1 Feb 2025 16:56:31 -0500 Subject: [PATCH 3/7] Add JniRuntime.JniTypeManager.GetInvokerType() Context: be6cc8fb13eec5079c0e3080be6e3b5ded70a6d1 This isn't strictly required *yet*, but it *should* be required as soon as we start trying to run dotnet/android unit tests within a NativeAOT environment, because dotnet/android still uses string-based Invoker lookup, as in a pre-be6cc8fb dotnet/java-interop world. Adding `GetInvokerType()` would allow e.g. `NativeAotTypeManager.GetInvokerType()` to "stringly" lookup and resolve types. (which might fail because of the trimmer, but at least this fallback path is *expressible*.) --- .../Java.Interop/JniRuntime.JniTypeManager.cs | 32 +++++++++++++++++++ .../JniRuntime.JniValueManager.cs | 31 +----------------- src/Java.Interop/PublicAPI.Unshipped.txt | 1 + 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs index 5f93dbe2b..089420f18 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs @@ -81,6 +81,7 @@ public override string ToString () public partial class JniTypeManager : IDisposable, ISetRuntime { + internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes; internal const DynamicallyAccessedMemberTypes MethodsConstructors = MethodsAndPrivateNested | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; @@ -385,6 +386,37 @@ IEnumerable CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe yield break; } + [return: DynamicallyAccessedMembers (Constructors)] + internal protected virtual Type? GetInvokerType ( + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + // https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186 + const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step."; + + [UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)] + [return: DynamicallyAccessedMembers (Constructors)] + static Type MakeGenericType ( + [DynamicallyAccessedMembers (Constructors)] + Type type, + Type [] arguments) => + // FIXME: https://github.com/dotnet/java-interop/issues/1192 + #pragma warning disable IL3050 + type.MakeGenericType (arguments); + #pragma warning restore IL3050 + + var signature = type.GetCustomAttribute (); + if (signature == null || signature.InvokerType == null) { + return null; + } + + Type[] arguments = type.GetGenericArguments (); + if (arguments.Length == 0) + return signature.InvokerType; + + return MakeGenericType (signature.InvokerType, arguments); + } + #if NET public IReadOnlyList? GetStaticMethodFallbackTypes (string jniSimpleReference) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 6c3d7c11a..447a974fb 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -391,7 +391,7 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) Type type) { if (type.IsAbstract || type.IsInterface) { - type = GetInvokerType (type) ?? type; + type = Runtime.TypeManager.GetInvokerType (type) ?? type; } return TryCreatePeer (ref reference, options, type); } @@ -420,35 +420,6 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) return null; } - [return: DynamicallyAccessedMembers (Constructors)] - static Type? GetInvokerType (Type type) - { - // https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186 - const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step."; - - [UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)] - [return: DynamicallyAccessedMembers (Constructors)] - static Type MakeGenericType ( - [DynamicallyAccessedMembers (Constructors)] - Type type, - Type [] arguments) => - // FIXME: https://github.com/dotnet/java-interop/issues/1192 - #pragma warning disable IL3050 - type.MakeGenericType (arguments); - #pragma warning restore IL3050 - - var signature = type.GetCustomAttribute (); - if (signature == null || signature.InvokerType == null) { - return null; - } - - Type[] arguments = type.GetGenericArguments (); - if (arguments.Length == 0) - return signature.InvokerType; - - return MakeGenericType (signature.InvokerType, arguments); - } - public object? CreateValue ( ref JniObjectReference reference, JniObjectReferenceOptions options, diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index d0b8563b2..6828753f7 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -3,6 +3,7 @@ static Java.Interop.JniEnvironment.BeginMarshalMethod(nint jnienv, out Java.Inte static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransition transition) -> void virtual Java.Interop.JniRuntime.OnEnterMarshalMethod() -> void virtual Java.Interop.JniRuntime.OnUserUnhandledException(ref Java.Interop.JniTransition transition, System.Exception! e) -> void +virtual Java.Interop.JniRuntime.JniTypeManager.GetInvokerType(System.Type! type) -> System.Type? virtual Java.Interop.JniRuntime.JniValueManager.TryCreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> Java.Interop.IJavaPeerable? Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void From 6994637d8150fb40e9fc1ab80f1b6715be30a702 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Sun, 2 Feb 2025 21:01:51 -0500 Subject: [PATCH 4/7] Make `GetInvokerType()` public. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …because why not? Related question about semantics: should it return null if there isn't an Invoker type? Or should it instead return `type` if it doesn't *need* an Invoker type? --- src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs index 089420f18..86702e43b 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs @@ -387,7 +387,7 @@ IEnumerable CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe } [return: DynamicallyAccessedMembers (Constructors)] - internal protected virtual Type? GetInvokerType ( + public virtual Type? GetInvokerType ( [DynamicallyAccessedMembers (Constructors)] Type type) { From 33cd9dd729c7a41d667e30163bf0716c0ae23416 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 3 Feb 2025 07:24:44 -0500 Subject: [PATCH 5/7] Previous commit asked about semantics. I'm still not sure if `GetInvokerType()` should *always* return a `Type`, or only return a type that is an invoker type. Leaving that aside, there is a question around parameter validation: there wasn't any before. A common (if inconsistently applied) pattern in java-interop is to have a public non-virtual method that does parameter validation, and then calls a protected virtual method that does the actual work. Apply that pattern here, calling the protected method `GetInvokerTypeCore()`. --- .../Java.Interop/JniRuntime.JniTypeManager.cs | 13 ++++++++++++- .../Java.Interop/JniRuntime.JniValueManager.cs | 4 +--- src/Java.Interop/PublicAPI.Unshipped.txt | 3 ++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs index 86702e43b..f501c0cf9 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs @@ -387,7 +387,18 @@ IEnumerable CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe } [return: DynamicallyAccessedMembers (Constructors)] - public virtual Type? GetInvokerType ( + public Type? GetInvokerType ( + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + if (type.IsAbstract || type.IsInterface) { + return GetInvokerTypeCore (type); + } + return null; + } + + [return: DynamicallyAccessedMembers (Constructors)] + protected virtual Type? GetInvokerTypeCore ( [DynamicallyAccessedMembers (Constructors)] Type type) { diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 447a974fb..eb9e23790 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -390,9 +390,7 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type) [DynamicallyAccessedMembers (Constructors)] Type type) { - if (type.IsAbstract || type.IsInterface) { - type = Runtime.TypeManager.GetInvokerType (type) ?? type; - } + type = Runtime.TypeManager.GetInvokerType (type) ?? type; return TryCreatePeer (ref reference, options, type); } diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index 6828753f7..f25b9f6bb 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -3,10 +3,11 @@ static Java.Interop.JniEnvironment.BeginMarshalMethod(nint jnienv, out Java.Inte static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransition transition) -> void virtual Java.Interop.JniRuntime.OnEnterMarshalMethod() -> void virtual Java.Interop.JniRuntime.OnUserUnhandledException(ref Java.Interop.JniTransition transition, System.Exception! e) -> void -virtual Java.Interop.JniRuntime.JniTypeManager.GetInvokerType(System.Type! type) -> System.Type? +virtual Java.Interop.JniRuntime.JniTypeManager.GetInvokerTypeCore(System.Type! type) -> System.Type? virtual Java.Interop.JniRuntime.JniValueManager.TryCreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> Java.Interop.IJavaPeerable? Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void +Java.Interop.JniRuntime.JniTypeManager.GetInvokerType(System.Type! type) -> System.Type? Java.Interop.JniRuntime.JniValueManager.GetPeer(Java.Interop.JniObjectReference reference, System.Type? targetType = null) -> Java.Interop.IJavaPeerable? Java.Interop.JniTypeSignatureAttribute.InvokerType.get -> System.Type? Java.Interop.JniTypeSignatureAttribute.InvokerType.set -> void From 74cb713610089cb33ccef58c639a2e4d93600400 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 3 Feb 2025 12:11:32 -0500 Subject: [PATCH 6/7] Add documentation, tests. --- .../JniRuntime.JniTypeManager.xml | 28 ++++++++++++++++ .../Java.Interop/JniRuntime.JniTypeManager.cs | 2 ++ .../JniRuntime.JniTypeManagerTests.cs | 33 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml create mode 100644 tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniTypeManagerTests.cs diff --git a/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml b/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml new file mode 100644 index 000000000..1bc46666e --- /dev/null +++ b/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml @@ -0,0 +1,28 @@ + + + + + Manages bound Java types. + + + + + + Gets the Invoker type for + + + An Invoker type is a concrete type which can be constructed, + which is used to invoke instances of abstract type that cannot be constructed. + For example, the interface type has no + constructor, but if a java.lang.Runnable instance enters managed code, + a Invoker must be constructed around the instance so that it may be used. + + + + If is an interface or abstract class, returns the + type which should be constructed around instances of . + If no such type exists, or if is a concrete type, + then is returned. + + + diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs index f501c0cf9..55731280b 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs @@ -79,6 +79,7 @@ public override string ToString () } #endif // NET + /// public partial class JniTypeManager : IDisposable, ISetRuntime { internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; @@ -386,6 +387,7 @@ IEnumerable CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe yield break; } + /// [return: DynamicallyAccessedMembers (Constructors)] public Type? GetInvokerType ( [DynamicallyAccessedMembers (Constructors)] diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniTypeManagerTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniTypeManagerTests.cs new file mode 100644 index 000000000..2fe3c2cf7 --- /dev/null +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniTypeManagerTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Reflection; +using System.Collections.Generic; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests { + + [TestFixture] + public class JniRuntimeJniTypeManagerTests : JavaVMFixture { + + [Test] + public void GetInvokerType () + { + using (var vm = new MyTypeManager ()) { + // Concrete type; no invoker + Assert.IsNull (vm.GetInvokerType (typeof (JavaObject))); + + // Not a bound abstract Java type; no invoker + Assert.IsNull (vm.GetInvokerType (typeof (System.ICloneable))); + + // Bound abstract Java type; has an invoker + Assert.AreSame (typeof (IJavaInterfaceInvoker), vm.GetInvokerType (typeof (IJavaInterface))); + } + } + + class MyTypeManager : JniRuntime.JniTypeManager { + } + } +} + From 4af29bcfb960379c84d01bc341f119ba304960bb Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 3 Feb 2025 12:14:48 -0500 Subject: [PATCH 7/7] Fix C# xmldocs --- .../Documentation/Java.Interop/JniRuntime.JniTypeManager.xml | 4 ++-- src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml b/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml index 1bc46666e..5adca3ece 100644 --- a/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml +++ b/src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniTypeManager.xml @@ -13,8 +13,8 @@ An Invoker type is a concrete type which can be constructed, which is used to invoke instances of abstract type that cannot be constructed. - For example, the interface type has no - constructor, but if a java.lang.Runnable instance enters managed code, + For example, the interface type Java.Lang.IRunnable cannot be constructed, + but if a java.lang.Runnable instance enters managed code, a Invoker must be constructed around the instance so that it may be used. diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs index 55731280b..0399a70e4 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs @@ -387,7 +387,7 @@ IEnumerable CreateGetTypesForSimpleReferenceEnumerator (string jniSimpleRe yield break; } - /// + /// [return: DynamicallyAccessedMembers (Constructors)] public Type? GetInvokerType ( [DynamicallyAccessedMembers (Constructors)]