From 3931347aa983c5f35b8b03da4e25212cdc910948 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 10 Jun 2026 16:37:11 +0200 Subject: [PATCH 01/20] Use singular type signature lookup in tests The runtime hot path uses GetTypeSignature(), and GetTypeSignatures() is no longer needed for this helper assertion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs index 198cb9c97..fce9e4ca2 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs @@ -103,14 +103,8 @@ static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeywo var info = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (type); Assert.IsTrue (info.IsValid, $"info.IsValid for `{type}`"); - // `GetTypeSignature() and `GetTypeSignatures()` should be "in sync"; verify that! - var info2 = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignatures (type).FirstOrDefault (); - Assert.IsTrue (info2.IsValid, $"info2.IsValid for `{type}`"); - Assert.AreEqual (jniType, info.Name, $"info.Name for `{type}`"); - Assert.AreEqual (jniType, info2.Name, $"info2.Name for `{type}`"); Assert.AreEqual (arrayRank, info.ArrayRank, $"info.ArrayRank for `{type}`"); - Assert.AreEqual (arrayRank, info2.ArrayRank, $"info2.ArrayRank for `{type}`"); } [Test] From 11e9f39842dc6ffddd76c69abdd106d83051b40b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Jun 2026 09:41:18 +0200 Subject: [PATCH 02/20] Reuse proxy value marshalers from base manager Move the object proxy value marshaler under JniValueManager so non-reflection value managers can reuse JavaProxyObject marshaling without duplicating the managed proxy type. Expose the existing object and peerable marshalers to derived value managers, and use ConditionalWeakTable.GetValue for JavaProxyObject caching. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JavaProxyObject.cs | 8 +-- .../Java.Interop/JniBuiltinMarshalers.cs | 2 +- .../JniRuntime.JniValueManager.cs | 71 +++++++++++++++++++ .../JniRuntime.ReflectionJniValueManager.cs | 64 +---------------- src/Java.Interop/PublicAPI.Unshipped.txt | 2 + 5 files changed, 76 insertions(+), 71 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JavaProxyObject.cs b/src/Java.Interop/Java.Interop/JavaProxyObject.cs index 0050cf291..f59f18b2d 100644 --- a/src/Java.Interop/Java.Interop/JavaProxyObject.cs +++ b/src/Java.Interop/Java.Interop/JavaProxyObject.cs @@ -63,13 +63,7 @@ public override bool Equals (object? obj) if (value == null) return null; - lock (CachedValues) { - if (CachedValues.TryGetValue (value, out var proxy)) - return proxy; - proxy = new JavaProxyObject (value); - CachedValues.Add (value, proxy); - return proxy; - } + return CachedValues.GetValue (value, static v => new JavaProxyObject (v)); } // TODO: Keep in sync with the code generated by ExportedMemberBuilder diff --git a/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs b/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs index 49e70cc31..2f063d28a 100644 --- a/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs +++ b/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs @@ -159,7 +159,7 @@ static KeyValuePair[] InitJniBuiltinMarshalers () { return new []{ new KeyValuePair(typeof (string), JniStringValueMarshaler.Instance), - new KeyValuePair(typeof (JavaProxyObject), ProxyValueMarshaler.Instance), + new KeyValuePair(typeof (JavaProxyObject), JniValueManager.ObjectValueMarshaler), new KeyValuePair(typeof (Boolean), JniBooleanValueMarshaler.Instance), new KeyValuePair(typeof (Boolean?), JniNullableBooleanValueMarshaler.Instance), new KeyValuePair(typeof (SByte), JniSByteValueMarshaler.Instance), diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 6ba8d5dae..1bc956762 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -190,6 +190,77 @@ protected virtual bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (tr return false; } + protected internal static JniValueMarshaler ObjectValueMarshaler => ProxyValueMarshaler.Instance; + protected internal static JniValueMarshaler PeerableValueMarshaler => JavaPeerableValueMarshaler.Instance; + + sealed class ProxyValueMarshaler : JniValueMarshaler { + + internal static ProxyValueMarshaler Instance = new ProxyValueMarshaler (); + + [return: MaybeNull] + public override object? CreateGenericValue ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType) + { + var jvm = JniEnvironment.Runtime; + + if (targetType == null || targetType == typeof (object)) { + var target = jvm.ValueManager.PeekValue (reference); + if (target != null) { + JniObjectReference.Dispose (ref reference, options); + return target; + } + + targetType = jvm.ValueManager.GetRuntimeType (reference); + } + if (targetType != null) { + var vm = jvm.ValueManager.GetValueMarshaler (targetType); + if (vm != Instance) { + return vm.CreateValue (ref reference, options, targetType)!; + } + } + + var existing = jvm.ValueManager.PeekValue (reference); + if (existing != null) { + JniObjectReference.Dispose (ref reference, options); + return existing; + } + // Punt! Hope it's a java.lang.Object + return jvm.ValueManager.CreatePeer (ref reference, options, targetType); + } + + public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull] object? value, ParameterAttributes synchronize) + { + if (value == null) + return new JniValueMarshalerState (); + + var jvm = JniEnvironment.Runtime; + + var vm = jvm.ValueManager.GetValueMarshaler (value.GetType ()); + if (vm != Instance) { + var s = vm.CreateObjectReferenceArgumentState (value, synchronize); + return new JniValueMarshalerState (s, vm); + } + + var p = JavaProxyObject.GetProxy (value); + return new JniValueMarshalerState (p!.PeerReference.NewLocalRef ()); + } + + public override void DestroyGenericArgumentState (object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + { + var vm = state.Extra as JniValueMarshaler; + if (vm != null) { + vm.DestroyArgumentState (value, ref state, synchronize); + return; + } + var r = state.ReferenceValue; + JniObjectReference.Dispose (ref r); + state = new JniValueMarshalerState (); + } + } + public IJavaPeerable? GetPeer ( JniObjectReference reference, [DynamicallyAccessedMembers (Constructors)] diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index 70ba47414..7fdb7c002 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -467,7 +467,7 @@ protected override JniValueMarshaler GetValueMarshalerCore (Type type) return JavaPeerableValueMarshaler.Instance; } - return ProxyValueMarshaler.Instance; + return ObjectValueMarshaler; } static Type? GetListType (Type type) @@ -663,66 +663,4 @@ public override Expression CreateReturnValueFromManagedExpression (JniValueMarsh return ValueMarshaler.CreateReturnValueFromManagedExpression (context, sourceValue); } } - - sealed class ProxyValueMarshaler : JniValueMarshaler { - - internal static ProxyValueMarshaler Instance = new ProxyValueMarshaler (); - - [return: MaybeNull] - public override object? CreateGenericValue ( - ref JniObjectReference reference, - JniObjectReferenceOptions options, - [DynamicallyAccessedMembers (Constructors)] - Type? targetType) - { - var jvm = JniEnvironment.Runtime; - - if (targetType == null || targetType == typeof (object)) { - targetType = jvm.ValueManager.GetRuntimeType (reference); - } - if (targetType != null) { - var vm = jvm.ValueManager.GetValueMarshaler (targetType); - if (vm != Instance) { - return vm.CreateValue (ref reference, options, targetType)!; - } - } - - var target = jvm.ValueManager.PeekValue (reference); - if (target != null) { - JniObjectReference.Dispose (ref reference, options); - return target; - } - // Punt! Hope it's a java.lang.Object - return jvm.ValueManager.CreatePeer (ref reference, options, targetType); - } - - public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]object? value, ParameterAttributes synchronize) - { - if (value == null) - return new JniValueMarshalerState (); - - var jvm = JniEnvironment.Runtime; - - var vm = jvm.ValueManager.GetValueMarshaler (value.GetType ()); - if (vm != Instance) { - var s = vm.CreateObjectReferenceArgumentState (value, synchronize); - return new JniValueMarshalerState (s, vm); - } - - var p = JavaProxyObject.GetProxy (value); - return new JniValueMarshalerState (p!.PeerReference.NewLocalRef ()); - } - - public override void DestroyGenericArgumentState (object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize) - { - var vm = state.Extra as JniValueMarshaler; - if (vm != null) { - vm.DestroyArgumentState (value, ref state, synchronize); - return; - } - var r = state.ReferenceValue; - JniObjectReference.Dispose (ref r); - state = new JniValueMarshalerState (); - } - } } diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index de95bcd1d..d8f6dda5d 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -9,6 +9,8 @@ virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeForSimpleReference(string! virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignatureCore(System.Type! type) -> Java.Interop.JniTypeSignature virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignaturesCore(System.Type! type) -> System.Collections.Generic.IEnumerable! static Java.Interop.JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers(Java.Interop.JniType! nativeClass, string! jniSimpleReference, System.ReadOnlySpan methods) -> bool +static Java.Interop.JniRuntime.JniValueManager.ObjectValueMarshaler.get -> Java.Interop.JniValueMarshaler! +static Java.Interop.JniRuntime.JniValueManager.PeerableValueMarshaler.get -> Java.Interop.JniValueMarshaler! 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.ReflectionJniTypeManager From 597f6da7b42179a424ec0631877ee628e4d14b60 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Jun 2026 11:29:28 +0200 Subject: [PATCH 03/20] Generalize shared primitive array marshalers Expose primitive array type signature and value marshaler lookup from the base managers so non-reflection value managers can reuse the full Java.Interop primitive array marshaler set. Mark expression-based value marshaler contract tests as unsupported for trimmable typemap consumers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JavaPrimitiveArrays.cs | 35 +++++++++- .../JniRuntime.JniValueManager.cs | 65 +++++++++++++++++++ .../JniRuntime.ReflectionJniTypeManager.cs | 16 +---- .../JniRuntime.ReflectionJniValueManager.cs | 56 +--------------- src/Java.Interop/PublicAPI.Unshipped.txt | 3 + .../JniValueMarshalerContractTests.cs | 1 + 6 files changed, 106 insertions(+), 70 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs b/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs index a7f06c76f..f82977128 100644 --- a/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs +++ b/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs @@ -13,7 +13,7 @@ namespace Java.Interop { partial class JniRuntime { - partial class ReflectionJniTypeManager { + partial class JniTypeManager { readonly struct JniPrimitiveArrayInfo { public readonly JniTypeSignature JniTypeSignature; public readonly Type PrimitiveType; @@ -38,7 +38,16 @@ public JniPrimitiveArrayInfo (string jniSimpleReference, Type primitiveType, par new ("D", typeof (Double), typeof (Double[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaDoubleArray)), }; - static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature) + internal static Type[] GetPrimitiveArrayTypes (Type primitiveType) + { + foreach (var e in JniPrimitiveArrayTypes) { + if (e.PrimitiveType == primitiveType) + return e.ArrayTypes; + } + throw new InvalidOperationException ($"Should not be reached; Could not find JniPrimitiveArrayInfo for {primitiveType}"); + } + + protected internal static bool TryGetPrimitiveArrayTypeSignature (Type type, out JniTypeSignature signature) { foreach (var e in JniPrimitiveArrayTypes) { if (Array.IndexOf (e.ArrayTypes, type) < 0) @@ -90,6 +99,28 @@ static KeyValuePair[] InitJniPrimitiveArrayMarshalers ( new KeyValuePair(typeof (JavaDoubleArray), JavaDoubleArray.ArrayMarshaler), }; } + + partial class JniValueManager { + protected internal static bool TryGetPrimitiveArrayValueMarshaler (Type type, [NotNullWhen (true)] out JniValueMarshaler? marshaler) + { + foreach (var entry in JniPrimitiveArrayMarshalers.Value) { + if (entry.Key == type || IsCompatiblePrimitiveListType (type, entry.Key)) { + marshaler = entry.Value; + return true; + } + } + + marshaler = null; + return false; + } + + static bool IsCompatiblePrimitiveListType (Type type, Type marshalerType) + { + return type.IsGenericType && + type.GetGenericTypeDefinition () == typeof (IList<>) && + type.IsAssignableFrom (marshalerType); + } + } } partial class JniEnvironment { diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 1bc956762..e773c83aa 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; +using Java.Interop.Expressions; namespace Java.Interop { @@ -193,6 +195,14 @@ protected virtual bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (tr protected internal static JniValueMarshaler ObjectValueMarshaler => ProxyValueMarshaler.Instance; protected internal static JniValueMarshaler PeerableValueMarshaler => JavaPeerableValueMarshaler.Instance; + protected internal static JniValueMarshaler CreateDelegatingValueMarshaler< + [DynamicallyAccessedMembers (Constructors)] + T + > (JniValueMarshaler valueMarshaler) + { + return new DelegatingValueMarshaler (valueMarshaler); + } + sealed class ProxyValueMarshaler : JniValueMarshaler { internal static ProxyValueMarshaler Instance = new ProxyValueMarshaler (); @@ -261,6 +271,61 @@ public override void DestroyGenericArgumentState (object? value, ref JniValueMar } } + sealed class DelegatingValueMarshaler< + [DynamicallyAccessedMembers (Constructors)] + T + > + : JniValueMarshaler + { + + JniValueMarshaler ValueMarshaler; + + public DelegatingValueMarshaler (JniValueMarshaler valueMarshaler) + { + ValueMarshaler = valueMarshaler; + } + + [return: MaybeNull] + public override T CreateGenericValue ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType) + { + return (T) ValueMarshaler.CreateValue (ref reference, options, targetType ?? typeof (T))!; + } + + public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull] T value, ParameterAttributes synchronize) + { + return ValueMarshaler.CreateObjectReferenceArgumentState (value, synchronize); + } + + public override void DestroyGenericArgumentState ([AllowNull] T value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + { + ValueMarshaler.DestroyArgumentState (value, ref state, synchronize); + } + + [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] + public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize) + { + return ValueMarshaler.CreateParameterFromManagedExpression (context, sourceValue, synchronize); + } + + [RequiresDynamicCode (ExpressionRequiresUnreferencedCode)] + [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] + public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type? targetType) + { + return ValueMarshaler.CreateParameterToManagedExpression (context, sourceValue, synchronize, targetType); + } + + [RequiresDynamicCode (ExpressionRequiresUnreferencedCode)] + [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] + public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue) + { + return ValueMarshaler.CreateReturnValueFromManagedExpression (context, sourceValue); + } + } + public IJavaPeerable? GetPeer ( JniObjectReference reference, [DynamicallyAccessedMembers (Constructors)] diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs index b98ec2843..373bf48cd 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs @@ -25,7 +25,7 @@ protected override JniTypeSignature GetTypeSignatureCore (Type type) JniTypeSignature signature = JniTypeSignature.Empty; if (GetBuiltInTypeSignature (type, ref signature)) return signature.AddArrayRank (rank); - if (GetBuiltInTypeArraySignature (type, ref signature)) + if (TryGetPrimitiveArrayTypeSignature (type, out signature)) return signature.AddArrayRank (rank); var isGeneric = type.IsGenericType; @@ -55,7 +55,7 @@ protected override IEnumerable GetTypeSignaturesCore (Type typ var signature = JniTypeSignature.Empty; if (GetBuiltInTypeSignature (type, ref signature)) yield return signature.AddArrayRank (rank); - if (GetBuiltInTypeArraySignature (type, ref signature)) + if (TryGetPrimitiveArrayTypeSignature (type, out signature)) yield return signature.AddArrayRank (rank); var isGeneric = type.IsGenericType; @@ -286,17 +286,7 @@ IEnumerable CreateGetTypesEnumerator (JniTypeSignature typeSignature) IEnumerable GetPrimitiveArrayTypesForSimpleReference (JniTypeSignature typeSignature, Type type) { - int index = -1; - for (int i = 0; i < JniPrimitiveArrayTypes.Length; ++i) { - if (JniPrimitiveArrayTypes [i].PrimitiveType == type) { - index = i; - break; - } - } - if (index == -1) { - throw new InvalidOperationException ($"Should not be reached; Could not find JniPrimitiveArrayInfo for {type}"); - } - foreach (var t in JniPrimitiveArrayTypes [index].ArrayTypes) { + foreach (var t in GetPrimitiveArrayTypes (type)) { var rank = typeSignature.ArrayRank-1; var arrayType = t; while (rank-- > 0) { diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index 7fdb7c002..e4f932db8 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -420,7 +420,7 @@ bool TryConstructPeer ( return r; lock (Marshalers) { if (!Marshalers.TryGetValue (typeof (T), out var d)) - Marshalers.Add (typeof (T), d = new DelegatingValueMarshaler (m)); + Marshalers.Add (typeof (T), d = CreateDelegatingValueMarshaler (m)); return (JniValueMarshaler) d; } } @@ -609,58 +609,4 @@ public override Expression CreateParameterToManagedExpression (JniValueMarshaler } } - sealed class DelegatingValueMarshaler< - [DynamicallyAccessedMembers (Constructors)] - T - > - : JniValueMarshaler - { - - JniValueMarshaler ValueMarshaler; - - public DelegatingValueMarshaler (JniValueMarshaler valueMarshaler) - { - ValueMarshaler = valueMarshaler; - } - - [return: MaybeNull] - public override T CreateGenericValue ( - ref JniObjectReference reference, - JniObjectReferenceOptions options, - [DynamicallyAccessedMembers (Constructors)] - Type? targetType) - { - return (T) ValueMarshaler.CreateValue (ref reference, options, targetType ?? typeof (T))!; - } - - public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]T value, ParameterAttributes synchronize) - { - return ValueMarshaler.CreateObjectReferenceArgumentState (value, synchronize); - } - - public override void DestroyGenericArgumentState ([AllowNull]T value, ref JniValueMarshalerState state, ParameterAttributes synchronize) - { - ValueMarshaler.DestroyArgumentState (value, ref state, synchronize); - } - - [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] - public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize) - { - return ValueMarshaler.CreateParameterFromManagedExpression (context, sourceValue, synchronize); - } - - [RequiresDynamicCode (ExpressionRequiresUnreferencedCode)] - [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] - public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type? targetType) - { - return ValueMarshaler.CreateParameterToManagedExpression (context, sourceValue, synchronize, targetType); - } - - [RequiresDynamicCode (ExpressionRequiresUnreferencedCode)] - [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] - public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue) - { - return ValueMarshaler.CreateReturnValueFromManagedExpression (context, sourceValue); - } - } } diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index d8f6dda5d..ab0ad51ce 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ #nullable enable static Java.Interop.JniEnvironment.BeginMarshalMethod(nint jnienv, out Java.Interop.JniTransition transition, out Java.Interop.JniRuntime? runtime) -> bool static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransition transition) -> void +static Java.Interop.JniRuntime.JniTypeManager.TryGetPrimitiveArrayTypeSignature(System.Type! type, out Java.Interop.JniTypeSignature signature) -> bool 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.GetInvokerTypeCore(System.Type! type) -> System.Type? @@ -9,8 +10,10 @@ virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeForSimpleReference(string! virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignatureCore(System.Type! type) -> Java.Interop.JniTypeSignature virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignaturesCore(System.Type! type) -> System.Collections.Generic.IEnumerable! static Java.Interop.JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers(Java.Interop.JniType! nativeClass, string! jniSimpleReference, System.ReadOnlySpan methods) -> bool +static Java.Interop.JniRuntime.JniValueManager.CreateDelegatingValueMarshaler(Java.Interop.JniValueMarshaler! valueMarshaler) -> Java.Interop.JniValueMarshaler! static Java.Interop.JniRuntime.JniValueManager.ObjectValueMarshaler.get -> Java.Interop.JniValueMarshaler! static Java.Interop.JniRuntime.JniValueManager.PeerableValueMarshaler.get -> Java.Interop.JniValueMarshaler! +static Java.Interop.JniRuntime.JniValueManager.TryGetPrimitiveArrayValueMarshaler(System.Type! type, out Java.Interop.JniValueMarshaler? marshaler) -> bool 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.ReflectionJniTypeManager diff --git a/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs index 3c5668b66..9205222f9 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs @@ -216,6 +216,7 @@ public void DestroyGenericArgumentState () } [Test] + [Category ("TrimmableTypeMapUnsupported")] [RequiresUnreferencedCode ("CreateReturnValueFromManagedExpression")] [RequiresDynamicCode ("CreateReturnValueFromManagedExpression")] public void CreateReturnValueFromManagedExpression () From 7add4b224a95026ff2f7abc77b7e44046109eaa4 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Jun 2026 12:12:36 +0200 Subject: [PATCH 04/20] Mark unsupported trimmable marshaler tests Keep JavaProxyObject cache access locked while using ConditionalWeakTable.GetValue. Make non-generic DestroyArgumentState tolerate null values for value-type marshalers, and mark Java.Interop tests that exercise expression-based marshaling, ManagedPeer, or legacy native registration as unsupported for trimmable typemap consumers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Java.Interop/Java.Interop/JavaProxyObject.cs | 4 +++- src/Java.Interop/Java.Interop/JniValueMarshaler.cs | 6 +++++- .../Java.Interop/InvokeVirtualFromConstructorTests.cs | 2 +- .../Java.Interop/JavaManagedGCBridgeTests.cs | 2 +- .../Java.Interop/JniInstanceMethodIDTest.cs | 2 +- .../Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs | 1 + .../Java.Interop/JniRuntimeJniValueManagerContract.cs | 1 + tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs | 2 +- tests/Java.Interop-Tests/Java.Interop/TestTypeTests.cs | 2 +- 9 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JavaProxyObject.cs b/src/Java.Interop/Java.Interop/JavaProxyObject.cs index f59f18b2d..4760bbd43 100644 --- a/src/Java.Interop/Java.Interop/JavaProxyObject.cs +++ b/src/Java.Interop/Java.Interop/JavaProxyObject.cs @@ -63,7 +63,9 @@ public override bool Equals (object? obj) if (value == null) return null; - return CachedValues.GetValue (value, static v => new JavaProxyObject (v)); + lock (CachedValues) { + return CachedValues.GetValue (value, static v => new JavaProxyObject (v)); + } } // TODO: Keep in sync with the code generated by ExportedMemberBuilder diff --git a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs index 19015c6ee..fa760e756 100644 --- a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs +++ b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs @@ -276,7 +276,11 @@ public override JniValueMarshalerState CreateObjectReferenceArgumentState (objec public override void DestroyArgumentState (object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0) { - DestroyGenericArgumentState ((T) value!, ref state, synchronize); + if (value == null) { + DestroyGenericArgumentState (default, ref state, synchronize); + return; + } + DestroyGenericArgumentState ((T) value, ref state, synchronize); } } } diff --git a/tests/Java.Interop-Tests/Java.Interop/InvokeVirtualFromConstructorTests.cs b/tests/Java.Interop-Tests/Java.Interop/InvokeVirtualFromConstructorTests.cs index e22dd86e7..949f19029 100644 --- a/tests/Java.Interop-Tests/Java.Interop/InvokeVirtualFromConstructorTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/InvokeVirtualFromConstructorTests.cs @@ -8,6 +8,7 @@ namespace Java.InteropTests { [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] #if !__ANDROID__ // We want stability around the CallVirtualFromConstructorDerived static fields [NonParallelizable] @@ -153,4 +154,3 @@ public void CreateJavaInstanceFirst () } } } - diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs index e0c127d6e..79b64ef2f 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs @@ -9,6 +9,7 @@ namespace Java.InteropTests { [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] public class JavaManagedGCBridgeTests : JavaVMFixture { #if !NO_GC_BRIDGE_SUPPORT @@ -66,4 +67,3 @@ protected override void Dispose (bool disposing) } } } - diff --git a/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs b/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs index 28e0dacab..54f9a947b 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs @@ -7,6 +7,7 @@ namespace Java.InteropTests { [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] public class JniInstanceMethodIDTest : JavaVMFixture { // https://code.google.com/p/android/issues/detail?id=65710 @@ -38,4 +39,3 @@ public unsafe void CallNonvirtualVoidMethod_WithBaseMethodIDAndDerivedType () } } } - diff --git a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs index 0cb2ac5d4..4103d94bf 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs @@ -8,6 +8,7 @@ namespace Java.InteropTests { [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] public class JniPeerMembersTests : JavaVMFixture { [Test] diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs index 25fa52bb7..62a13403b 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs @@ -19,6 +19,7 @@ namespace Java.InteropTests { // Modifies JniRuntime.valueManager instance field; can't be done in parallel [NonParallelizable] #endif // !__ANDROID__ + [Category ("TrimmableTypeMapUnsupported")] public abstract class JniRuntimeJniValueManagerContract : JavaVMFixture { [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] diff --git a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs index 34c284f2a..05e8156fa 100644 --- a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs @@ -7,6 +7,7 @@ namespace Java.InteropTests { [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] public class MethodBindingTests : JavaVMFixture { // @@ -76,4 +77,3 @@ public void VirtualMethodBinding () } } } - diff --git a/tests/Java.Interop-Tests/Java.Interop/TestTypeTests.cs b/tests/Java.Interop-Tests/Java.Interop/TestTypeTests.cs index ef0db0747..b65e6f084 100644 --- a/tests/Java.Interop-Tests/Java.Interop/TestTypeTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/TestTypeTests.cs @@ -9,6 +9,7 @@ namespace Java.InteropTests { #if !NO_MARSHAL_MEMBER_BUILDER_SUPPORT [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] public class TestTypeTests : JavaVMFixture { int lrefStartCount; @@ -154,4 +155,3 @@ public void PropogateException () } #endif // !NO_MARSHAL_MEMBER_BUILDER_SUPPORT } - From b594f16552f997823af45ffd9f82f723465af537 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Jun 2026 15:51:56 +0200 Subject: [PATCH 05/20] Enable GC bridge test for trimmable Android Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs index 79b64ef2f..a48443a23 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs @@ -9,7 +9,6 @@ namespace Java.InteropTests { [TestFixture] - [Category ("TrimmableTypeMapUnsupported")] public class JavaManagedGCBridgeTests : JavaVMFixture { #if !NO_GC_BRIDGE_SUPPORT From f9fe3470e71a79e7b54f9e08b7794ba627211b2d Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Jun 2026 16:01:43 +0200 Subject: [PATCH 06/20] Enable method binding tests for trimmable Android Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs | 1 - tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs | 1 - tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs b/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs index 54f9a947b..2949f1428 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs @@ -7,7 +7,6 @@ namespace Java.InteropTests { [TestFixture] - [Category ("TrimmableTypeMapUnsupported")] public class JniInstanceMethodIDTest : JavaVMFixture { // https://code.google.com/p/android/issues/detail?id=65710 diff --git a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs index 4103d94bf..0cb2ac5d4 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs @@ -8,7 +8,6 @@ namespace Java.InteropTests { [TestFixture] - [Category ("TrimmableTypeMapUnsupported")] public class JniPeerMembersTests : JavaVMFixture { [Test] diff --git a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs index 05e8156fa..de7838700 100644 --- a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs @@ -7,7 +7,6 @@ namespace Java.InteropTests { [TestFixture] - [Category ("TrimmableTypeMapUnsupported")] public class MethodBindingTests : JavaVMFixture { // From 9bef884ca7b61e810a2944444beb0b8a29631547 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Jun 2026 17:23:03 +0200 Subject: [PATCH 07/20] Enable value marshaler expression tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JniValueMarshalerContractTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs index 9205222f9..5926174df 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs @@ -216,7 +216,6 @@ public void DestroyGenericArgumentState () } [Test] - [Category ("TrimmableTypeMapUnsupported")] [RequiresUnreferencedCode ("CreateReturnValueFromManagedExpression")] [RequiresDynamicCode ("CreateReturnValueFromManagedExpression")] public void CreateReturnValueFromManagedExpression () @@ -236,7 +235,7 @@ public void CreateReturnValueFromManagedExpression () protected virtual string GetExpectedReturnValueFromManagedExpression (string jvm, string value, Expression ret) { var valueType = GetTypeName (typeof (T)); - var marshalerType = marshaler.GetType ().Name; + var marshalerType = GetTypeName (marshaler.GetType ()); return $@"{{ JniRuntime {jvm}; {valueType} {value}; @@ -700,4 +699,3 @@ class JniValueMarshaler_DemoValueType_ContractTests : JniValueMarshalerContractT protected override bool IsJniValueType {get {return true;}} } } - From 94243c8f4b905f17b3f3bd86daeb84fd18579bb8 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Jun 2026 17:51:13 +0200 Subject: [PATCH 08/20] Mark custom marshaler tests unsupported when trimmable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JniValueMarshalerContractTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs index 5926174df..095c4467d 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs @@ -693,6 +693,7 @@ public override void DestroyGenericArgumentState (DemoValueType value, ref JniVa } [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] class JniValueMarshaler_DemoValueType_ContractTests : JniValueMarshalerContractTests { protected override DemoValueType Value {get {return new DemoValueType (42);}} From df2aa0a78bd02588797b5b66b6aebf10e1c8de54 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 10:49:04 +0200 Subject: [PATCH 09/20] Mark ManagedPeer-dependent tests unsupported when trimmable These tests rely on desktop ManagedPeer construction/registration paths that the Android trimmable typemap intentionally does not support. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs | 1 + tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs | 1 + tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs | 1 + tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs | 1 + tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs | 1 + 5 files changed, 5 insertions(+) diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs index a48443a23..79b64ef2f 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs @@ -9,6 +9,7 @@ namespace Java.InteropTests { [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] public class JavaManagedGCBridgeTests : JavaVMFixture { #if !NO_GC_BRIDGE_SUPPORT diff --git a/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs b/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs index 2949f1428..bce118ca8 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniInstanceMethodIDTest.cs @@ -11,6 +11,7 @@ public class JniInstanceMethodIDTest : JavaVMFixture { // https://code.google.com/p/android/issues/detail?id=65710 [Test] + [Category ("TrimmableTypeMapUnsupported")] public unsafe void CallNonvirtualVoidMethod_WithBaseMethodIDAndDerivedType () { using (var b = new JniType ("net/dot/jni/test/CallNonvirtualBase")) diff --git a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs index 0cb2ac5d4..43bbac364 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs @@ -76,6 +76,7 @@ public void MethodLookupForNonexistentStaticMethodWillTryFallbacks () [Test] [Category ("NativeAOTIgnore")] + [Category ("TrimmableTypeMapUnsupported")] public void ReplacementTypeUsedForMethodLookup () { using var o = new RenameClassDerived (); diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs index 48b3e1663..18aed78d4 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs @@ -73,6 +73,7 @@ public void CreateJavaVMWithNullBuilder () } [Test] + [Category ("TrimmableTypeMapUnsupported")] public void BuiltInSimpleReferenceMap_ContainsManagedPeerByDefault () { var types = JniRuntime.CurrentRuntime.TypeManager.GetTypes (new JniTypeSignature (ManagedPeer.JniTypeName)); diff --git a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs index de7838700..05e8156fa 100644 --- a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs @@ -7,6 +7,7 @@ namespace Java.InteropTests { [TestFixture] + [Category ("TrimmableTypeMapUnsupported")] public class MethodBindingTests : JavaVMFixture { // From 0208f3ad4c38027dd5453779b00df8cc55604308 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 12:20:57 +0200 Subject: [PATCH 10/20] Simplify trimmable marshaler follow-up Keep value marshaler helper classes as top-level internals and remove the primitive-array/type-manager helper API churn from the Java.Interop branch. Android keeps the trimmable-only primitive array handling locally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JavaPrimitiveArrays.cs | 35 +---- .../Java.Interop/JavaProxyObject.cs | 6 +- .../Java.Interop/JniBuiltinMarshalers.cs | 2 +- .../JniRuntime.JniValueManager.cs | 125 ------------------ .../JniRuntime.ReflectionJniTypeManager.cs | 16 ++- .../JniRuntime.ReflectionJniValueManager.cs | 120 ++++++++++++++++- src/Java.Interop/PublicAPI.Unshipped.txt | 2 - 7 files changed, 139 insertions(+), 167 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs b/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs index f82977128..a7f06c76f 100644 --- a/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs +++ b/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs @@ -13,7 +13,7 @@ namespace Java.Interop { partial class JniRuntime { - partial class JniTypeManager { + partial class ReflectionJniTypeManager { readonly struct JniPrimitiveArrayInfo { public readonly JniTypeSignature JniTypeSignature; public readonly Type PrimitiveType; @@ -38,16 +38,7 @@ public JniPrimitiveArrayInfo (string jniSimpleReference, Type primitiveType, par new ("D", typeof (Double), typeof (Double[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaDoubleArray)), }; - internal static Type[] GetPrimitiveArrayTypes (Type primitiveType) - { - foreach (var e in JniPrimitiveArrayTypes) { - if (e.PrimitiveType == primitiveType) - return e.ArrayTypes; - } - throw new InvalidOperationException ($"Should not be reached; Could not find JniPrimitiveArrayInfo for {primitiveType}"); - } - - protected internal static bool TryGetPrimitiveArrayTypeSignature (Type type, out JniTypeSignature signature) + static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature) { foreach (var e in JniPrimitiveArrayTypes) { if (Array.IndexOf (e.ArrayTypes, type) < 0) @@ -99,28 +90,6 @@ static KeyValuePair[] InitJniPrimitiveArrayMarshalers ( new KeyValuePair(typeof (JavaDoubleArray), JavaDoubleArray.ArrayMarshaler), }; } - - partial class JniValueManager { - protected internal static bool TryGetPrimitiveArrayValueMarshaler (Type type, [NotNullWhen (true)] out JniValueMarshaler? marshaler) - { - foreach (var entry in JniPrimitiveArrayMarshalers.Value) { - if (entry.Key == type || IsCompatiblePrimitiveListType (type, entry.Key)) { - marshaler = entry.Value; - return true; - } - } - - marshaler = null; - return false; - } - - static bool IsCompatiblePrimitiveListType (Type type, Type marshalerType) - { - return type.IsGenericType && - type.GetGenericTypeDefinition () == typeof (IList<>) && - type.IsAssignableFrom (marshalerType); - } - } } partial class JniEnvironment { diff --git a/src/Java.Interop/Java.Interop/JavaProxyObject.cs b/src/Java.Interop/Java.Interop/JavaProxyObject.cs index 4760bbd43..0050cf291 100644 --- a/src/Java.Interop/Java.Interop/JavaProxyObject.cs +++ b/src/Java.Interop/Java.Interop/JavaProxyObject.cs @@ -64,7 +64,11 @@ public override bool Equals (object? obj) return null; lock (CachedValues) { - return CachedValues.GetValue (value, static v => new JavaProxyObject (v)); + if (CachedValues.TryGetValue (value, out var proxy)) + return proxy; + proxy = new JavaProxyObject (value); + CachedValues.Add (value, proxy); + return proxy; } } diff --git a/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs b/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs index 2f063d28a..49e70cc31 100644 --- a/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs +++ b/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs @@ -159,7 +159,7 @@ static KeyValuePair[] InitJniBuiltinMarshalers () { return new []{ new KeyValuePair(typeof (string), JniStringValueMarshaler.Instance), - new KeyValuePair(typeof (JavaProxyObject), JniValueManager.ObjectValueMarshaler), + new KeyValuePair(typeof (JavaProxyObject), ProxyValueMarshaler.Instance), new KeyValuePair(typeof (Boolean), JniBooleanValueMarshaler.Instance), new KeyValuePair(typeof (Boolean?), JniNullableBooleanValueMarshaler.Instance), new KeyValuePair(typeof (SByte), JniSByteValueMarshaler.Instance), diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index e773c83aa..df76e5aad 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -3,10 +3,8 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -using Java.Interop.Expressions; namespace Java.Interop { @@ -203,129 +201,6 @@ protected internal static JniValueMarshaler CreateDelegatingValueMarshaler< return new DelegatingValueMarshaler (valueMarshaler); } - sealed class ProxyValueMarshaler : JniValueMarshaler { - - internal static ProxyValueMarshaler Instance = new ProxyValueMarshaler (); - - [return: MaybeNull] - public override object? CreateGenericValue ( - ref JniObjectReference reference, - JniObjectReferenceOptions options, - [DynamicallyAccessedMembers (Constructors)] - Type? targetType) - { - var jvm = JniEnvironment.Runtime; - - if (targetType == null || targetType == typeof (object)) { - var target = jvm.ValueManager.PeekValue (reference); - if (target != null) { - JniObjectReference.Dispose (ref reference, options); - return target; - } - - targetType = jvm.ValueManager.GetRuntimeType (reference); - } - if (targetType != null) { - var vm = jvm.ValueManager.GetValueMarshaler (targetType); - if (vm != Instance) { - return vm.CreateValue (ref reference, options, targetType)!; - } - } - - var existing = jvm.ValueManager.PeekValue (reference); - if (existing != null) { - JniObjectReference.Dispose (ref reference, options); - return existing; - } - // Punt! Hope it's a java.lang.Object - return jvm.ValueManager.CreatePeer (ref reference, options, targetType); - } - - public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull] object? value, ParameterAttributes synchronize) - { - if (value == null) - return new JniValueMarshalerState (); - - var jvm = JniEnvironment.Runtime; - - var vm = jvm.ValueManager.GetValueMarshaler (value.GetType ()); - if (vm != Instance) { - var s = vm.CreateObjectReferenceArgumentState (value, synchronize); - return new JniValueMarshalerState (s, vm); - } - - var p = JavaProxyObject.GetProxy (value); - return new JniValueMarshalerState (p!.PeerReference.NewLocalRef ()); - } - - public override void DestroyGenericArgumentState (object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize) - { - var vm = state.Extra as JniValueMarshaler; - if (vm != null) { - vm.DestroyArgumentState (value, ref state, synchronize); - return; - } - var r = state.ReferenceValue; - JniObjectReference.Dispose (ref r); - state = new JniValueMarshalerState (); - } - } - - sealed class DelegatingValueMarshaler< - [DynamicallyAccessedMembers (Constructors)] - T - > - : JniValueMarshaler - { - - JniValueMarshaler ValueMarshaler; - - public DelegatingValueMarshaler (JniValueMarshaler valueMarshaler) - { - ValueMarshaler = valueMarshaler; - } - - [return: MaybeNull] - public override T CreateGenericValue ( - ref JniObjectReference reference, - JniObjectReferenceOptions options, - [DynamicallyAccessedMembers (Constructors)] - Type? targetType) - { - return (T) ValueMarshaler.CreateValue (ref reference, options, targetType ?? typeof (T))!; - } - - public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull] T value, ParameterAttributes synchronize) - { - return ValueMarshaler.CreateObjectReferenceArgumentState (value, synchronize); - } - - public override void DestroyGenericArgumentState ([AllowNull] T value, ref JniValueMarshalerState state, ParameterAttributes synchronize) - { - ValueMarshaler.DestroyArgumentState (value, ref state, synchronize); - } - - [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] - public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize) - { - return ValueMarshaler.CreateParameterFromManagedExpression (context, sourceValue, synchronize); - } - - [RequiresDynamicCode (ExpressionRequiresUnreferencedCode)] - [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] - public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type? targetType) - { - return ValueMarshaler.CreateParameterToManagedExpression (context, sourceValue, synchronize, targetType); - } - - [RequiresDynamicCode (ExpressionRequiresUnreferencedCode)] - [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] - public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue) - { - return ValueMarshaler.CreateReturnValueFromManagedExpression (context, sourceValue); - } - } - public IJavaPeerable? GetPeer ( JniObjectReference reference, [DynamicallyAccessedMembers (Constructors)] diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs index 373bf48cd..b98ec2843 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniTypeManager.cs @@ -25,7 +25,7 @@ protected override JniTypeSignature GetTypeSignatureCore (Type type) JniTypeSignature signature = JniTypeSignature.Empty; if (GetBuiltInTypeSignature (type, ref signature)) return signature.AddArrayRank (rank); - if (TryGetPrimitiveArrayTypeSignature (type, out signature)) + if (GetBuiltInTypeArraySignature (type, ref signature)) return signature.AddArrayRank (rank); var isGeneric = type.IsGenericType; @@ -55,7 +55,7 @@ protected override IEnumerable GetTypeSignaturesCore (Type typ var signature = JniTypeSignature.Empty; if (GetBuiltInTypeSignature (type, ref signature)) yield return signature.AddArrayRank (rank); - if (TryGetPrimitiveArrayTypeSignature (type, out signature)) + if (GetBuiltInTypeArraySignature (type, ref signature)) yield return signature.AddArrayRank (rank); var isGeneric = type.IsGenericType; @@ -286,7 +286,17 @@ IEnumerable CreateGetTypesEnumerator (JniTypeSignature typeSignature) IEnumerable GetPrimitiveArrayTypesForSimpleReference (JniTypeSignature typeSignature, Type type) { - foreach (var t in GetPrimitiveArrayTypes (type)) { + int index = -1; + for (int i = 0; i < JniPrimitiveArrayTypes.Length; ++i) { + if (JniPrimitiveArrayTypes [i].PrimitiveType == type) { + index = i; + break; + } + } + if (index == -1) { + throw new InvalidOperationException ($"Should not be reached; Could not find JniPrimitiveArrayInfo for {type}"); + } + foreach (var t in JniPrimitiveArrayTypes [index].ArrayTypes) { var rank = typeSignature.ArrayRank-1; var arrayType = t; while (rank-- > 0) { diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index e4f932db8..70ba47414 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -420,7 +420,7 @@ bool TryConstructPeer ( return r; lock (Marshalers) { if (!Marshalers.TryGetValue (typeof (T), out var d)) - Marshalers.Add (typeof (T), d = CreateDelegatingValueMarshaler (m)); + Marshalers.Add (typeof (T), d = new DelegatingValueMarshaler (m)); return (JniValueMarshaler) d; } } @@ -467,7 +467,7 @@ protected override JniValueMarshaler GetValueMarshalerCore (Type type) return JavaPeerableValueMarshaler.Instance; } - return ObjectValueMarshaler; + return ProxyValueMarshaler.Instance; } static Type? GetListType (Type type) @@ -609,4 +609,120 @@ public override Expression CreateParameterToManagedExpression (JniValueMarshaler } } + sealed class DelegatingValueMarshaler< + [DynamicallyAccessedMembers (Constructors)] + T + > + : JniValueMarshaler + { + + JniValueMarshaler ValueMarshaler; + + public DelegatingValueMarshaler (JniValueMarshaler valueMarshaler) + { + ValueMarshaler = valueMarshaler; + } + + [return: MaybeNull] + public override T CreateGenericValue ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType) + { + return (T) ValueMarshaler.CreateValue (ref reference, options, targetType ?? typeof (T))!; + } + + public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]T value, ParameterAttributes synchronize) + { + return ValueMarshaler.CreateObjectReferenceArgumentState (value, synchronize); + } + + public override void DestroyGenericArgumentState ([AllowNull]T value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + { + ValueMarshaler.DestroyArgumentState (value, ref state, synchronize); + } + + [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] + public override Expression CreateParameterFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize) + { + return ValueMarshaler.CreateParameterFromManagedExpression (context, sourceValue, synchronize); + } + + [RequiresDynamicCode (ExpressionRequiresUnreferencedCode)] + [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] + public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type? targetType) + { + return ValueMarshaler.CreateParameterToManagedExpression (context, sourceValue, synchronize, targetType); + } + + [RequiresDynamicCode (ExpressionRequiresUnreferencedCode)] + [RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)] + public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue) + { + return ValueMarshaler.CreateReturnValueFromManagedExpression (context, sourceValue); + } + } + + sealed class ProxyValueMarshaler : JniValueMarshaler { + + internal static ProxyValueMarshaler Instance = new ProxyValueMarshaler (); + + [return: MaybeNull] + public override object? CreateGenericValue ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType) + { + var jvm = JniEnvironment.Runtime; + + if (targetType == null || targetType == typeof (object)) { + targetType = jvm.ValueManager.GetRuntimeType (reference); + } + if (targetType != null) { + var vm = jvm.ValueManager.GetValueMarshaler (targetType); + if (vm != Instance) { + return vm.CreateValue (ref reference, options, targetType)!; + } + } + + var target = jvm.ValueManager.PeekValue (reference); + if (target != null) { + JniObjectReference.Dispose (ref reference, options); + return target; + } + // Punt! Hope it's a java.lang.Object + return jvm.ValueManager.CreatePeer (ref reference, options, targetType); + } + + public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState ([MaybeNull]object? value, ParameterAttributes synchronize) + { + if (value == null) + return new JniValueMarshalerState (); + + var jvm = JniEnvironment.Runtime; + + var vm = jvm.ValueManager.GetValueMarshaler (value.GetType ()); + if (vm != Instance) { + var s = vm.CreateObjectReferenceArgumentState (value, synchronize); + return new JniValueMarshalerState (s, vm); + } + + var p = JavaProxyObject.GetProxy (value); + return new JniValueMarshalerState (p!.PeerReference.NewLocalRef ()); + } + + public override void DestroyGenericArgumentState (object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + { + var vm = state.Extra as JniValueMarshaler; + if (vm != null) { + vm.DestroyArgumentState (value, ref state, synchronize); + return; + } + var r = state.ReferenceValue; + JniObjectReference.Dispose (ref r); + state = new JniValueMarshalerState (); + } + } } diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index ab0ad51ce..3293beb9a 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -1,7 +1,6 @@ #nullable enable static Java.Interop.JniEnvironment.BeginMarshalMethod(nint jnienv, out Java.Interop.JniTransition transition, out Java.Interop.JniRuntime? runtime) -> bool static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransition transition) -> void -static Java.Interop.JniRuntime.JniTypeManager.TryGetPrimitiveArrayTypeSignature(System.Type! type, out Java.Interop.JniTypeSignature signature) -> bool 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.GetInvokerTypeCore(System.Type! type) -> System.Type? @@ -13,7 +12,6 @@ static Java.Interop.JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers(Ja static Java.Interop.JniRuntime.JniValueManager.CreateDelegatingValueMarshaler(Java.Interop.JniValueMarshaler! valueMarshaler) -> Java.Interop.JniValueMarshaler! static Java.Interop.JniRuntime.JniValueManager.ObjectValueMarshaler.get -> Java.Interop.JniValueMarshaler! static Java.Interop.JniRuntime.JniValueManager.PeerableValueMarshaler.get -> Java.Interop.JniValueMarshaler! -static Java.Interop.JniRuntime.JniValueManager.TryGetPrimitiveArrayValueMarshaler(System.Type! type, out Java.Interop.JniValueMarshaler? marshaler) -> bool 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.ReflectionJniTypeManager From 4aae0bd35b31fa547ef20767504a0d41f31a5c8a Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 13:08:05 +0200 Subject: [PATCH 11/20] Re-enable GC bridge test for trimmable Android Give CrossReferenceBridge explicit JniPeerMembers so Android can construct the intended Java peer without relying on the desktop ManagedPeer constructor path, and categorize the test as GCBridge instead of unsupported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JavaManagedGCBridgeTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs index 79b64ef2f..2e4ced232 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs @@ -9,7 +9,7 @@ namespace Java.InteropTests { [TestFixture] - [Category ("TrimmableTypeMapUnsupported")] + [Category ("GCBridge")] public class JavaManagedGCBridgeTests : JavaVMFixture { #if !NO_GC_BRIDGE_SUPPORT @@ -58,6 +58,11 @@ static void SetupLinks (JavaObjectArray array, out WeakRef [JniTypeSignature (JniTypeName, GenerateJavaPeer=false)] public class CrossReferenceBridge : JavaObject { internal const string JniTypeName = "net/dot/jni/test/CrossReferenceBridge"; + static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (CrossReferenceBridge)); + + public override JniPeerMembers JniPeerMembers { + get {return _members;} + } public string id; public List link = new List (); From 67b9796744b7480625ef7c736ea35506356c0eea Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 13:18:10 +0200 Subject: [PATCH 12/20] Re-enable replacement method lookup test The Android trimmable lane supports method replacement; keep ReplacementTypeUsedForMethodLookup active by categorizing it as Remapping instead of TrimmableTypeMapUnsupported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs index 43bbac364..e49aab6ba 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs @@ -76,7 +76,7 @@ public void MethodLookupForNonexistentStaticMethodWillTryFallbacks () [Test] [Category ("NativeAOTIgnore")] - [Category ("TrimmableTypeMapUnsupported")] + [Category ("Remapping")] public void ReplacementTypeUsedForMethodLookup () { using var o = new RenameClassDerived (); From 96ad3f94fa708e1c1839ce8b98559ec64f6ba7c1 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 13:23:36 +0200 Subject: [PATCH 13/20] Re-enable method binding test for trimmable Android The Android trimmable lane supports this dispatch scenario once the ManagedPeer-dependent Java fixtures are replaced, so categorize MethodBindingTests as MethodBinding instead of unsupported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs index 05e8156fa..1b602669f 100644 --- a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs @@ -7,7 +7,7 @@ namespace Java.InteropTests { [TestFixture] - [Category ("TrimmableTypeMapUnsupported")] + [Category ("MethodBinding")] public class MethodBindingTests : JavaVMFixture { // From 23f9e07061b800df8652dfaa33330c748574865b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 14:02:55 +0200 Subject: [PATCH 14/20] Keep ManagedPeer-dependent tests unsupported Remove the Android-trimmable fixture workarounds from the Java.Interop branch and leave ManagedPeer-dependent tests categorized as TrimmableTypeMapUnsupported. Restore JniValueMarshaler and JniTypeManagerTests changes so this PR stays focused. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Java.Interop/Java.Interop/JniValueMarshaler.cs | 6 +----- .../Java.Interop/JavaManagedGCBridgeTests.cs | 7 +------ .../Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs | 2 +- .../Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs | 6 ++++++ .../Java.Interop-Tests/Java.Interop/MethodBindingTests.cs | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs index fa760e756..19015c6ee 100644 --- a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs +++ b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs @@ -276,11 +276,7 @@ public override JniValueMarshalerState CreateObjectReferenceArgumentState (objec public override void DestroyArgumentState (object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0) { - if (value == null) { - DestroyGenericArgumentState (default, ref state, synchronize); - return; - } - DestroyGenericArgumentState ((T) value, ref state, synchronize); + DestroyGenericArgumentState ((T) value!, ref state, synchronize); } } } diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs index 2e4ced232..79b64ef2f 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaManagedGCBridgeTests.cs @@ -9,7 +9,7 @@ namespace Java.InteropTests { [TestFixture] - [Category ("GCBridge")] + [Category ("TrimmableTypeMapUnsupported")] public class JavaManagedGCBridgeTests : JavaVMFixture { #if !NO_GC_BRIDGE_SUPPORT @@ -58,11 +58,6 @@ static void SetupLinks (JavaObjectArray array, out WeakRef [JniTypeSignature (JniTypeName, GenerateJavaPeer=false)] public class CrossReferenceBridge : JavaObject { internal const string JniTypeName = "net/dot/jni/test/CrossReferenceBridge"; - static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (CrossReferenceBridge)); - - public override JniPeerMembers JniPeerMembers { - get {return _members;} - } public string id; public List link = new List (); diff --git a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs index e49aab6ba..43bbac364 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs @@ -76,7 +76,7 @@ public void MethodLookupForNonexistentStaticMethodWillTryFallbacks () [Test] [Category ("NativeAOTIgnore")] - [Category ("Remapping")] + [Category ("TrimmableTypeMapUnsupported")] public void ReplacementTypeUsedForMethodLookup () { using var o = new RenameClassDerived (); diff --git a/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs index fce9e4ca2..198cb9c97 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs @@ -103,8 +103,14 @@ static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeywo var info = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (type); Assert.IsTrue (info.IsValid, $"info.IsValid for `{type}`"); + // `GetTypeSignature() and `GetTypeSignatures()` should be "in sync"; verify that! + var info2 = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignatures (type).FirstOrDefault (); + Assert.IsTrue (info2.IsValid, $"info2.IsValid for `{type}`"); + Assert.AreEqual (jniType, info.Name, $"info.Name for `{type}`"); + Assert.AreEqual (jniType, info2.Name, $"info2.Name for `{type}`"); Assert.AreEqual (arrayRank, info.ArrayRank, $"info.ArrayRank for `{type}`"); + Assert.AreEqual (arrayRank, info2.ArrayRank, $"info2.ArrayRank for `{type}`"); } [Test] diff --git a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs index 1b602669f..05e8156fa 100644 --- a/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/MethodBindingTests.cs @@ -7,7 +7,7 @@ namespace Java.InteropTests { [TestFixture] - [Category ("MethodBinding")] + [Category ("TrimmableTypeMapUnsupported")] public class MethodBindingTests : JavaVMFixture { // From 99b4840f4ec1ef9c00dfe0b38d0050d75ce8b771 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 14:22:20 +0200 Subject: [PATCH 15/20] Improve code formatting --- .../Java.Interop/JniRuntime.JniValueManager.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index df76e5aad..84b477d85 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -190,16 +190,14 @@ protected virtual bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (tr return false; } - protected internal static JniValueMarshaler ObjectValueMarshaler => ProxyValueMarshaler.Instance; - protected internal static JniValueMarshaler PeerableValueMarshaler => JavaPeerableValueMarshaler.Instance; + protected internal static JniValueMarshaler ObjectValueMarshaler + => ProxyValueMarshaler.Instance; - protected internal static JniValueMarshaler CreateDelegatingValueMarshaler< - [DynamicallyAccessedMembers (Constructors)] - T - > (JniValueMarshaler valueMarshaler) - { - return new DelegatingValueMarshaler (valueMarshaler); - } + protected internal static JniValueMarshaler PeerableValueMarshaler + => JavaPeerableValueMarshaler.Instance; + + protected internal static JniValueMarshaler CreateDelegatingValueMarshaler<[DynamicallyAccessedMembers (Constructors)] T> (JniValueMarshaler valueMarshaler) + => new DelegatingValueMarshaler (valueMarshaler); public IJavaPeerable? GetPeer ( JniObjectReference reference, From 1466cbe15e615841916c7896ed78a97d7aa0cb98 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 14:55:31 +0200 Subject: [PATCH 16/20] Remove unnecessary method --- src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs | 3 --- src/Java.Interop/PublicAPI.Unshipped.txt | 1 - 2 files changed, 4 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 84b477d85..ede94b849 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -196,9 +196,6 @@ protected internal static JniValueMarshaler ObjectValueMarshaler protected internal static JniValueMarshaler PeerableValueMarshaler => JavaPeerableValueMarshaler.Instance; - protected internal static JniValueMarshaler CreateDelegatingValueMarshaler<[DynamicallyAccessedMembers (Constructors)] T> (JniValueMarshaler valueMarshaler) - => new DelegatingValueMarshaler (valueMarshaler); - public IJavaPeerable? GetPeer ( JniObjectReference reference, [DynamicallyAccessedMembers (Constructors)] diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index 3293beb9a..d8f6dda5d 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -9,7 +9,6 @@ virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeForSimpleReference(string! virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignatureCore(System.Type! type) -> Java.Interop.JniTypeSignature virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignaturesCore(System.Type! type) -> System.Collections.Generic.IEnumerable! static Java.Interop.JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers(Java.Interop.JniType! nativeClass, string! jniSimpleReference, System.ReadOnlySpan methods) -> bool -static Java.Interop.JniRuntime.JniValueManager.CreateDelegatingValueMarshaler(Java.Interop.JniValueMarshaler! valueMarshaler) -> Java.Interop.JniValueMarshaler! static Java.Interop.JniRuntime.JniValueManager.ObjectValueMarshaler.get -> Java.Interop.JniValueMarshaler! static Java.Interop.JniRuntime.JniValueManager.PeerableValueMarshaler.get -> Java.Interop.JniValueMarshaler! Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void From cf298e6232b50c33ffcc00a78d5994a829799456 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 15:16:59 +0200 Subject: [PATCH 17/20] Move array marshaler state APIs to value manager Let JavaObjectArray request value marshaler state directly from JniValueManager. ReflectionJniValueManager keeps using value marshalers internally, while trimmable Android can override the abstract state APIs without exposing value marshalers on that path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JavaObjectArray.cs | 13 ++-- .../JniRuntime.JniValueManager.cs | 66 +++++++++++++++++++ .../JniRuntime.ReflectionJniValueManager.cs | 36 ++++++++++ src/Java.Interop/PublicAPI.Unshipped.txt | 12 ++++ 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JavaObjectArray.cs b/src/Java.Interop/Java.Interop/JavaObjectArray.cs index c5a559e33..cad37d36d 100644 --- a/src/Java.Interop/Java.Interop/JavaObjectArray.cs +++ b/src/Java.Interop/Java.Interop/JavaObjectArray.cs @@ -89,10 +89,10 @@ T GetElementAt (int index) void SetElementAt (int index, T value) { - var vm = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (); - var s = vm.CreateGenericObjectReferenceArgumentState (value); + var vm = JniEnvironment.Runtime.ValueManager; + var s = vm.CreateObjectReferenceValueMarshalerState (value); JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, index, s.ReferenceValue); - vm.DestroyGenericArgumentState (value, ref s, 0); + vm.DestroyValueMarshalerState (value, ref s, 0); } public override IEnumerator GetEnumerator () @@ -108,13 +108,13 @@ public override IEnumerator GetEnumerator () public override void Clear () { int len = Length; - var vm = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (); + var vm = JniEnvironment.Runtime.ValueManager; #pragma warning disable 8653 - var s = vm.CreateArgumentState (default (T)); + var s = vm.CreateValueMarshalerState (default (T)); for (int i = 0; i < len; i++) { JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, i, s.ReferenceValue); } - vm.DestroyGenericArgumentState (default (T), ref s, 0); + vm.DestroyValueMarshalerState (default (T), ref s, 0); #pragma warning restore 8653 } @@ -222,4 +222,3 @@ public static JavaObjectArray? CreateMarshalObjectArray< } } } - diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index ede94b849..f950b5de3 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -309,6 +309,72 @@ protected internal static JniValueMarshaler PeerableValueMarshal public JniValueMarshaler GetValueMarshaler<[DynamicallyAccessedMembers (Constructors)] T> () => GetValueMarshalerCore (); protected abstract JniValueMarshaler GetValueMarshalerCore<[DynamicallyAccessedMembers (Constructors)] T> (); + + internal JniValueMarshalerState CreateValueMarshalerState (Type type, object? value, ParameterAttributes synchronize = ParameterAttributes.In) + { + if (type == null) + throw new ArgumentNullException (nameof (type)); + return CreateValueMarshalerStateCore (type, value, synchronize); + } + + protected abstract JniValueMarshalerState CreateValueMarshalerStateCore (Type type, object? value, ParameterAttributes synchronize); + + internal JniValueMarshalerState CreateValueMarshalerState< + [DynamicallyAccessedMembers (Constructors)] + T + > ([MaybeNull] T value, ParameterAttributes synchronize = ParameterAttributes.In) + { + return CreateValueMarshalerStateCore (value, synchronize); + } + + protected abstract JniValueMarshalerState CreateValueMarshalerStateCore< + [DynamicallyAccessedMembers (Constructors)] + T + > ([MaybeNull] T value, ParameterAttributes synchronize); + + internal JniValueMarshalerState CreateObjectReferenceValueMarshalerState (Type type, object? value, ParameterAttributes synchronize = 0) + { + if (type == null) + throw new ArgumentNullException (nameof (type)); + return CreateObjectReferenceValueMarshalerStateCore (type, value, synchronize); + } + + protected abstract JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore (Type type, object? value, ParameterAttributes synchronize); + + internal JniValueMarshalerState CreateObjectReferenceValueMarshalerState< + [DynamicallyAccessedMembers (Constructors)] + T + > ([MaybeNull] T value, ParameterAttributes synchronize = 0) + { + return CreateObjectReferenceValueMarshalerStateCore (value, synchronize); + } + + protected abstract JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore< + [DynamicallyAccessedMembers (Constructors)] + T + > ([MaybeNull] T value, ParameterAttributes synchronize); + + internal void DestroyValueMarshalerState (Type type, object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0) + { + if (type == null) + throw new ArgumentNullException (nameof (type)); + DestroyValueMarshalerStateCore (type, value, ref state, synchronize); + } + + protected abstract void DestroyValueMarshalerStateCore (Type type, object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize); + + internal void DestroyValueMarshalerState< + [DynamicallyAccessedMembers (Constructors)] + T + > ([AllowNull] T value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0) + { + DestroyValueMarshalerStateCore (value, ref state, synchronize); + } + + protected abstract void DestroyValueMarshalerStateCore< + [DynamicallyAccessedMembers (Constructors)] + T + > ([AllowNull] T value, ref JniValueMarshalerState state, ParameterAttributes synchronize); } } } diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index 70ba47414..e1cc0178b 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -470,6 +470,42 @@ protected override JniValueMarshaler GetValueMarshalerCore (Type type) return ProxyValueMarshaler.Instance; } + protected override JniValueMarshalerState CreateValueMarshalerStateCore (Type type, object? value, ParameterAttributes synchronize) + { + EnsureNotDisposed (); + return GetValueMarshaler (type).CreateArgumentState (value, synchronize); + } + + protected override JniValueMarshalerState CreateValueMarshalerStateCore<[DynamicallyAccessedMembers (Constructors)] T> ([MaybeNull] T value, ParameterAttributes synchronize) + { + EnsureNotDisposed (); + return GetValueMarshaler ().CreateGenericArgumentState (value, synchronize); + } + + protected override JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore (Type type, object? value, ParameterAttributes synchronize) + { + EnsureNotDisposed (); + return GetValueMarshaler (type).CreateObjectReferenceArgumentState (value, synchronize); + } + + protected override JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore<[DynamicallyAccessedMembers (Constructors)] T> ([MaybeNull] T value, ParameterAttributes synchronize) + { + EnsureNotDisposed (); + return GetValueMarshaler ().CreateGenericObjectReferenceArgumentState (value, synchronize); + } + + protected override void DestroyValueMarshalerStateCore (Type type, object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + { + EnsureNotDisposed (); + GetValueMarshaler (type).DestroyArgumentState (value, ref state, synchronize); + } + + protected override void DestroyValueMarshalerStateCore<[DynamicallyAccessedMembers (Constructors)] T> ([AllowNull] T value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + { + EnsureNotDisposed (); + GetValueMarshaler ().DestroyGenericArgumentState (value, ref state, synchronize); + } + static Type? GetListType (Type type) { foreach (var iface in type.GetInterfaces ().Concat (new [] { type })) { diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index d8f6dda5d..1593d051d 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -11,6 +11,12 @@ virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignaturesCore(System.Type static Java.Interop.JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers(Java.Interop.JniType! nativeClass, string! jniSimpleReference, System.ReadOnlySpan methods) -> bool static Java.Interop.JniRuntime.JniValueManager.ObjectValueMarshaler.get -> Java.Interop.JniValueMarshaler! static Java.Interop.JniRuntime.JniValueManager.PeerableValueMarshaler.get -> Java.Interop.JniValueMarshaler! +abstract Java.Interop.JniRuntime.JniValueManager.CreateObjectReferenceValueMarshalerStateCore(System.Type! type, object? value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState +abstract Java.Interop.JniRuntime.JniValueManager.CreateObjectReferenceValueMarshalerStateCore(T value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState +abstract Java.Interop.JniRuntime.JniValueManager.CreateValueMarshalerStateCore(System.Type! type, object? value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState +abstract Java.Interop.JniRuntime.JniValueManager.CreateValueMarshalerStateCore(T value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState +abstract Java.Interop.JniRuntime.JniValueManager.DestroyValueMarshalerStateCore(System.Type! type, object? value, ref Java.Interop.JniValueMarshalerState state, System.Reflection.ParameterAttributes synchronize) -> void +abstract Java.Interop.JniRuntime.JniValueManager.DestroyValueMarshalerStateCore(T value, ref Java.Interop.JniValueMarshalerState state, System.Reflection.ParameterAttributes synchronize) -> void 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.ReflectionJniTypeManager @@ -67,8 +73,14 @@ abstract Java.Interop.JniRuntime.JniValueManager.GetValueMarshalerCore(System.Ty override Java.Interop.JniRuntime.ReflectionJniValueManager.ActivatePeer(Java.Interop.JniObjectReference reference, System.Type! type, System.Reflection.ConstructorInfo! cinfo, object?[]? argumentValues) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.ConstructPeerCore(Java.Interop.IJavaPeerable! peer, ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.CreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, System.Type? targetType) -> Java.Interop.IJavaPeerable? +override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateObjectReferenceValueMarshalerStateCore(System.Type! type, object? value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState +override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateObjectReferenceValueMarshalerStateCore(T value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> object? override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> T +override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueMarshalerStateCore(System.Type! type, object? value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState +override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueMarshalerStateCore(T value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState +override Java.Interop.JniRuntime.ReflectionJniValueManager.DestroyValueMarshalerStateCore(System.Type! type, object? value, ref Java.Interop.JniValueMarshalerState state, System.Reflection.ParameterAttributes synchronize) -> void +override Java.Interop.JniRuntime.ReflectionJniValueManager.DestroyValueMarshalerStateCore(T value, ref Java.Interop.JniValueMarshalerState state, System.Reflection.ParameterAttributes synchronize) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> object? override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> T override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueMarshalerCore(System.Type! type) -> Java.Interop.JniValueMarshaler! From 26a5615352043f776792869e16469eaa61968f85 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 17:06:39 +0200 Subject: [PATCH 18/20] Simplify object array marshaler state Keep JavaObjectArray object-reference marshaling on JniValueManager while removing unused state overloads, default-value state APIs, and exposed proxy marshaler accessors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JavaObjectArray.cs | 12 +-- .../JniRuntime.JniValueManager.cs | 83 +++++-------------- .../JniRuntime.ReflectionJniValueManager.cs | 52 +++++++----- .../Java.Interop/JniValueMarshaler.cs | 2 +- src/Java.Interop/PublicAPI.Unshipped.txt | 18 +--- 5 files changed, 63 insertions(+), 104 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JavaObjectArray.cs b/src/Java.Interop/Java.Interop/JavaObjectArray.cs index cad37d36d..f50a1d390 100644 --- a/src/Java.Interop/Java.Interop/JavaObjectArray.cs +++ b/src/Java.Interop/Java.Interop/JavaObjectArray.cs @@ -90,9 +90,9 @@ T GetElementAt (int index) void SetElementAt (int index, T value) { var vm = JniEnvironment.Runtime.ValueManager; - var s = vm.CreateObjectReferenceValueMarshalerState (value); + var s = vm.CreateObjectReferenceValueMarshalerState (typeof (T), value); JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, index, s.ReferenceValue); - vm.DestroyValueMarshalerState (value, ref s, 0); + vm.DestroyValueMarshalerState (ref s); } public override IEnumerator GetEnumerator () @@ -108,14 +108,10 @@ public override IEnumerator GetEnumerator () public override void Clear () { int len = Length; - var vm = JniEnvironment.Runtime.ValueManager; -#pragma warning disable 8653 - var s = vm.CreateValueMarshalerState (default (T)); + var nullReference = new JniObjectReference (); for (int i = 0; i < len; i++) { - JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, i, s.ReferenceValue); + JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, i, nullReference); } - vm.DestroyValueMarshalerState (default (T), ref s, 0); -#pragma warning restore 8653 } public override int IndexOf (T item) diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index f950b5de3..5b049b9cd 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -190,12 +190,6 @@ protected virtual bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (tr return false; } - protected internal static JniValueMarshaler ObjectValueMarshaler - => ProxyValueMarshaler.Instance; - - protected internal static JniValueMarshaler PeerableValueMarshaler - => JavaPeerableValueMarshaler.Instance; - public IJavaPeerable? GetPeer ( JniObjectReference reference, [DynamicallyAccessedMembers (Constructors)] @@ -228,6 +222,8 @@ protected internal static JniValueMarshaler PeerableValueMarshal [DynamicallyAccessedMembers (Constructors)] Type? targetType = null) { + EnsureNotDisposed (); + return CreateValueCore (ref reference, options, targetType); } @@ -238,6 +234,8 @@ protected internal static JniValueMarshaler PeerableValueMarshal [DynamicallyAccessedMembers (Constructors)] Type? targetType = null) { + EnsureNotDisposed (); + return CreateValueCore (ref reference, options, targetType); } @@ -260,6 +258,8 @@ protected internal static JniValueMarshaler PeerableValueMarshal [DynamicallyAccessedMembers (Constructors)] Type? targetType = null) { + EnsureNotDisposed (); + return GetValueCore (ref reference, options, targetType); } @@ -277,6 +277,8 @@ protected internal static JniValueMarshaler PeerableValueMarshal [DynamicallyAccessedMembers (Constructors)] Type? targetType = null) { + EnsureNotDisposed (); + return GetValueCore (ref reference, options, targetType); } @@ -310,71 +312,30 @@ protected internal static JniValueMarshaler PeerableValueMarshal public JniValueMarshaler GetValueMarshaler<[DynamicallyAccessedMembers (Constructors)] T> () => GetValueMarshalerCore (); protected abstract JniValueMarshaler GetValueMarshalerCore<[DynamicallyAccessedMembers (Constructors)] T> (); - internal JniValueMarshalerState CreateValueMarshalerState (Type type, object? value, ParameterAttributes synchronize = ParameterAttributes.In) - { - if (type == null) - throw new ArgumentNullException (nameof (type)); - return CreateValueMarshalerStateCore (type, value, synchronize); - } - - protected abstract JniValueMarshalerState CreateValueMarshalerStateCore (Type type, object? value, ParameterAttributes synchronize); - - internal JniValueMarshalerState CreateValueMarshalerState< - [DynamicallyAccessedMembers (Constructors)] - T - > ([MaybeNull] T value, ParameterAttributes synchronize = ParameterAttributes.In) - { - return CreateValueMarshalerStateCore (value, synchronize); - } - - protected abstract JniValueMarshalerState CreateValueMarshalerStateCore< - [DynamicallyAccessedMembers (Constructors)] - T - > ([MaybeNull] T value, ParameterAttributes synchronize); - - internal JniValueMarshalerState CreateObjectReferenceValueMarshalerState (Type type, object? value, ParameterAttributes synchronize = 0) - { - if (type == null) - throw new ArgumentNullException (nameof (type)); - return CreateObjectReferenceValueMarshalerStateCore (type, value, synchronize); - } - - protected abstract JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore (Type type, object? value, ParameterAttributes synchronize); - - internal JniValueMarshalerState CreateObjectReferenceValueMarshalerState< - [DynamicallyAccessedMembers (Constructors)] - T - > ([MaybeNull] T value, ParameterAttributes synchronize = 0) + internal JniValueMarshalerState CreateObjectReferenceValueMarshalerState ( + [DynamicallyAccessedMembers (Constructors)] + Type type, + object? value) { - return CreateObjectReferenceValueMarshalerStateCore (value, synchronize); - } - - protected abstract JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore< - [DynamicallyAccessedMembers (Constructors)] - T - > ([MaybeNull] T value, ParameterAttributes synchronize); + EnsureNotDisposed (); - internal void DestroyValueMarshalerState (Type type, object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0) - { if (type == null) throw new ArgumentNullException (nameof (type)); - DestroyValueMarshalerStateCore (type, value, ref state, synchronize); + return CreateObjectReferenceValueMarshalerStateCore (type, value); } - protected abstract void DestroyValueMarshalerStateCore (Type type, object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize); + protected abstract JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore ( + [DynamicallyAccessedMembers (Constructors)] + Type type, + object? value); - internal void DestroyValueMarshalerState< - [DynamicallyAccessedMembers (Constructors)] - T - > ([AllowNull] T value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0) + internal void DestroyValueMarshalerState (ref JniValueMarshalerState state) { - DestroyValueMarshalerStateCore (value, ref state, synchronize); + EnsureNotDisposed (); + DestroyValueMarshalerStateCore (ref state); } - protected abstract void DestroyValueMarshalerStateCore< - [DynamicallyAccessedMembers (Constructors)] - T - > ([AllowNull] T value, ref JniValueMarshalerState state, ParameterAttributes synchronize); + protected abstract void DestroyValueMarshalerStateCore (ref JniValueMarshalerState state); } } } diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index e1cc0178b..f11f89f5b 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -470,40 +470,52 @@ protected override JniValueMarshaler GetValueMarshalerCore (Type type) return ProxyValueMarshaler.Instance; } - protected override JniValueMarshalerState CreateValueMarshalerStateCore (Type type, object? value, ParameterAttributes synchronize) + protected override JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore ( + [DynamicallyAccessedMembers (Constructors)] + Type type, + object? value) { EnsureNotDisposed (); - return GetValueMarshaler (type).CreateArgumentState (value, synchronize); + var marshaler = GetValueMarshaler (type); + return CreateValueMarshalerState (marshaler, value, marshaler.CreateObjectReferenceArgumentState (value)); } - protected override JniValueMarshalerState CreateValueMarshalerStateCore<[DynamicallyAccessedMembers (Constructors)] T> ([MaybeNull] T value, ParameterAttributes synchronize) + protected override void DestroyValueMarshalerStateCore (ref JniValueMarshalerState state) { EnsureNotDisposed (); - return GetValueMarshaler ().CreateGenericArgumentState (value, synchronize); - } + if (state.Extra is not ValueMarshalerStateCleanup cleanup) { + var r = state.ReferenceValue; + JniObjectReference.Dispose (ref r); + state = new JniValueMarshalerState (); + return; + } - protected override JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore (Type type, object? value, ParameterAttributes synchronize) - { - EnsureNotDisposed (); - return GetValueMarshaler (type).CreateObjectReferenceArgumentState (value, synchronize); + cleanup.Destroy (ref state); } - protected override JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore<[DynamicallyAccessedMembers (Constructors)] T> ([MaybeNull] T value, ParameterAttributes synchronize) + static JniValueMarshalerState CreateValueMarshalerState (JniValueMarshaler marshaler, object? value, JniValueMarshalerState state) { - EnsureNotDisposed (); - return GetValueMarshaler ().CreateGenericObjectReferenceArgumentState (value, synchronize); + return new JniValueMarshalerState (state, new ValueMarshalerStateCleanup (marshaler, value, state.Extra)); } - protected override void DestroyValueMarshalerStateCore (Type type, object? value, ref JniValueMarshalerState state, ParameterAttributes synchronize) + sealed class ValueMarshalerStateCleanup { - EnsureNotDisposed (); - GetValueMarshaler (type).DestroyArgumentState (value, ref state, synchronize); - } + readonly JniValueMarshaler marshaler; + readonly object? value; + readonly object? extra; - protected override void DestroyValueMarshalerStateCore<[DynamicallyAccessedMembers (Constructors)] T> ([AllowNull] T value, ref JniValueMarshalerState state, ParameterAttributes synchronize) - { - EnsureNotDisposed (); - GetValueMarshaler ().DestroyGenericArgumentState (value, ref state, synchronize); + public ValueMarshalerStateCleanup (JniValueMarshaler marshaler, object? value, object? extra) + { + this.marshaler = marshaler; + this.value = value; + this.extra = extra; + } + + public void Destroy (ref JniValueMarshalerState state) + { + state = new JniValueMarshalerState (state, extra); + marshaler.DestroyArgumentState (value, ref state); + } } static Type? GetListType (Type type) diff --git a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs index 19015c6ee..56a5c66f5 100644 --- a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs +++ b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs @@ -81,7 +81,7 @@ internal JniValueMarshalerState (JniValueMarshalerState copy, object? extra = nu JniArgumentValue = copy.JniArgumentValue; ReferenceValue = copy.ReferenceValue; PeerableValue = copy.PeerableValue; - Extra = extra ?? copy.Extra; + Extra = extra; } public override int GetHashCode () diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index 1593d051d..ddb3af8c2 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -9,14 +9,8 @@ virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeForSimpleReference(string! virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignatureCore(System.Type! type) -> Java.Interop.JniTypeSignature virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignaturesCore(System.Type! type) -> System.Collections.Generic.IEnumerable! static Java.Interop.JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers(Java.Interop.JniType! nativeClass, string! jniSimpleReference, System.ReadOnlySpan methods) -> bool -static Java.Interop.JniRuntime.JniValueManager.ObjectValueMarshaler.get -> Java.Interop.JniValueMarshaler! -static Java.Interop.JniRuntime.JniValueManager.PeerableValueMarshaler.get -> Java.Interop.JniValueMarshaler! -abstract Java.Interop.JniRuntime.JniValueManager.CreateObjectReferenceValueMarshalerStateCore(System.Type! type, object? value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState -abstract Java.Interop.JniRuntime.JniValueManager.CreateObjectReferenceValueMarshalerStateCore(T value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState -abstract Java.Interop.JniRuntime.JniValueManager.CreateValueMarshalerStateCore(System.Type! type, object? value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState -abstract Java.Interop.JniRuntime.JniValueManager.CreateValueMarshalerStateCore(T value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState -abstract Java.Interop.JniRuntime.JniValueManager.DestroyValueMarshalerStateCore(System.Type! type, object? value, ref Java.Interop.JniValueMarshalerState state, System.Reflection.ParameterAttributes synchronize) -> void -abstract Java.Interop.JniRuntime.JniValueManager.DestroyValueMarshalerStateCore(T value, ref Java.Interop.JniValueMarshalerState state, System.Reflection.ParameterAttributes synchronize) -> void +abstract Java.Interop.JniRuntime.JniValueManager.CreateObjectReferenceValueMarshalerStateCore(System.Type! type, object? value) -> Java.Interop.JniValueMarshalerState +abstract Java.Interop.JniRuntime.JniValueManager.DestroyValueMarshalerStateCore(ref Java.Interop.JniValueMarshalerState state) -> void 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.ReflectionJniTypeManager @@ -73,14 +67,10 @@ abstract Java.Interop.JniRuntime.JniValueManager.GetValueMarshalerCore(System.Ty override Java.Interop.JniRuntime.ReflectionJniValueManager.ActivatePeer(Java.Interop.JniObjectReference reference, System.Type! type, System.Reflection.ConstructorInfo! cinfo, object?[]? argumentValues) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.ConstructPeerCore(Java.Interop.IJavaPeerable! peer, ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.CreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, System.Type? targetType) -> Java.Interop.IJavaPeerable? -override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateObjectReferenceValueMarshalerStateCore(System.Type! type, object? value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState -override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateObjectReferenceValueMarshalerStateCore(T value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState +override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateObjectReferenceValueMarshalerStateCore(System.Type! type, object? value) -> Java.Interop.JniValueMarshalerState override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> object? override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> T -override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueMarshalerStateCore(System.Type! type, object? value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState -override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueMarshalerStateCore(T value, System.Reflection.ParameterAttributes synchronize) -> Java.Interop.JniValueMarshalerState -override Java.Interop.JniRuntime.ReflectionJniValueManager.DestroyValueMarshalerStateCore(System.Type! type, object? value, ref Java.Interop.JniValueMarshalerState state, System.Reflection.ParameterAttributes synchronize) -> void -override Java.Interop.JniRuntime.ReflectionJniValueManager.DestroyValueMarshalerStateCore(T value, ref Java.Interop.JniValueMarshalerState state, System.Reflection.ParameterAttributes synchronize) -> void +override Java.Interop.JniRuntime.ReflectionJniValueManager.DestroyValueMarshalerStateCore(ref Java.Interop.JniValueMarshalerState state) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> object? override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> T override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueMarshalerCore(System.Type! type) -> Java.Interop.JniValueMarshaler! From fcd73dbcc62a854f293004a8ca9748266e7d1603 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 18:37:26 +0200 Subject: [PATCH 19/20] Return object references for object array marshaling Replace object-array marshaler state cleanup with a direct JniObjectReference result. The reflection manager now uses the existing marshaler to create and destroy temporary state internally before returning an independent local reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JavaObjectArray.cs | 9 ++-- .../JniRuntime.JniValueManager.cs | 14 ++---- .../JniRuntime.ReflectionJniValueManager.cs | 46 ++++--------------- .../Java.Interop/JniValueMarshaler.cs | 2 +- src/Java.Interop/PublicAPI.Unshipped.txt | 6 +-- 5 files changed, 20 insertions(+), 57 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JavaObjectArray.cs b/src/Java.Interop/Java.Interop/JavaObjectArray.cs index f50a1d390..0f8ecced2 100644 --- a/src/Java.Interop/Java.Interop/JavaObjectArray.cs +++ b/src/Java.Interop/Java.Interop/JavaObjectArray.cs @@ -90,9 +90,12 @@ T GetElementAt (int index) void SetElementAt (int index, T value) { var vm = JniEnvironment.Runtime.ValueManager; - var s = vm.CreateObjectReferenceValueMarshalerState (typeof (T), value); - JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, index, s.ReferenceValue); - vm.DestroyValueMarshalerState (ref s); + var r = vm.CreateObjectReferenceArgument (typeof (T), value); + try { + JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, index, r); + } finally { + JniObjectReference.Dispose (ref r); + } } public override IEnumerator GetEnumerator () diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 5b049b9cd..278654e70 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -312,7 +312,7 @@ protected virtual bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (tr public JniValueMarshaler GetValueMarshaler<[DynamicallyAccessedMembers (Constructors)] T> () => GetValueMarshalerCore (); protected abstract JniValueMarshaler GetValueMarshalerCore<[DynamicallyAccessedMembers (Constructors)] T> (); - internal JniValueMarshalerState CreateObjectReferenceValueMarshalerState ( + internal JniObjectReference CreateObjectReferenceArgument ( [DynamicallyAccessedMembers (Constructors)] Type type, object? value) @@ -321,21 +321,13 @@ internal JniValueMarshalerState CreateObjectReferenceValueMarshalerState ( if (type == null) throw new ArgumentNullException (nameof (type)); - return CreateObjectReferenceValueMarshalerStateCore (type, value); + return CreateObjectReferenceArgumentCore (type, value); } - protected abstract JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore ( + protected abstract JniObjectReference CreateObjectReferenceArgumentCore ( [DynamicallyAccessedMembers (Constructors)] Type type, object? value); - - internal void DestroyValueMarshalerState (ref JniValueMarshalerState state) - { - EnsureNotDisposed (); - DestroyValueMarshalerStateCore (ref state); - } - - protected abstract void DestroyValueMarshalerStateCore (ref JniValueMarshalerState state); } } } diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index f11f89f5b..faed9853a 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -470,50 +470,20 @@ protected override JniValueMarshaler GetValueMarshalerCore (Type type) return ProxyValueMarshaler.Instance; } - protected override JniValueMarshalerState CreateObjectReferenceValueMarshalerStateCore ( + protected override JniObjectReference CreateObjectReferenceArgumentCore ( [DynamicallyAccessedMembers (Constructors)] Type type, object? value) { EnsureNotDisposed (); var marshaler = GetValueMarshaler (type); - return CreateValueMarshalerState (marshaler, value, marshaler.CreateObjectReferenceArgumentState (value)); - } - - protected override void DestroyValueMarshalerStateCore (ref JniValueMarshalerState state) - { - EnsureNotDisposed (); - if (state.Extra is not ValueMarshalerStateCleanup cleanup) { - var r = state.ReferenceValue; - JniObjectReference.Dispose (ref r); - state = new JniValueMarshalerState (); - return; - } - - cleanup.Destroy (ref state); - } - - static JniValueMarshalerState CreateValueMarshalerState (JniValueMarshaler marshaler, object? value, JniValueMarshalerState state) - { - return new JniValueMarshalerState (state, new ValueMarshalerStateCleanup (marshaler, value, state.Extra)); - } - - sealed class ValueMarshalerStateCleanup - { - readonly JniValueMarshaler marshaler; - readonly object? value; - readonly object? extra; - - public ValueMarshalerStateCleanup (JniValueMarshaler marshaler, object? value, object? extra) - { - this.marshaler = marshaler; - this.value = value; - this.extra = extra; - } - - public void Destroy (ref JniValueMarshalerState state) - { - state = new JniValueMarshalerState (state, extra); + var state = marshaler.CreateObjectReferenceArgumentState (value); + try { + if (!state.ReferenceValue.IsValid) { + return new JniObjectReference (); + } + return state.ReferenceValue.NewLocalRef (); + } finally { marshaler.DestroyArgumentState (value, ref state); } } diff --git a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs index 56a5c66f5..19015c6ee 100644 --- a/src/Java.Interop/Java.Interop/JniValueMarshaler.cs +++ b/src/Java.Interop/Java.Interop/JniValueMarshaler.cs @@ -81,7 +81,7 @@ internal JniValueMarshalerState (JniValueMarshalerState copy, object? extra = nu JniArgumentValue = copy.JniArgumentValue; ReferenceValue = copy.ReferenceValue; PeerableValue = copy.PeerableValue; - Extra = extra; + Extra = extra ?? copy.Extra; } public override int GetHashCode () diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index ddb3af8c2..802cdd08f 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -9,8 +9,7 @@ virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeForSimpleReference(string! virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignatureCore(System.Type! type) -> Java.Interop.JniTypeSignature virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignaturesCore(System.Type! type) -> System.Collections.Generic.IEnumerable! static Java.Interop.JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers(Java.Interop.JniType! nativeClass, string! jniSimpleReference, System.ReadOnlySpan methods) -> bool -abstract Java.Interop.JniRuntime.JniValueManager.CreateObjectReferenceValueMarshalerStateCore(System.Type! type, object? value) -> Java.Interop.JniValueMarshalerState -abstract Java.Interop.JniRuntime.JniValueManager.DestroyValueMarshalerStateCore(ref Java.Interop.JniValueMarshalerState state) -> void +abstract Java.Interop.JniRuntime.JniValueManager.CreateObjectReferenceArgumentCore(System.Type! type, object? value) -> Java.Interop.JniObjectReference 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.ReflectionJniTypeManager @@ -67,10 +66,9 @@ abstract Java.Interop.JniRuntime.JniValueManager.GetValueMarshalerCore(System.Ty override Java.Interop.JniRuntime.ReflectionJniValueManager.ActivatePeer(Java.Interop.JniObjectReference reference, System.Type! type, System.Reflection.ConstructorInfo! cinfo, object?[]? argumentValues) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.ConstructPeerCore(Java.Interop.IJavaPeerable! peer, ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.CreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, System.Type? targetType) -> Java.Interop.IJavaPeerable? -override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateObjectReferenceValueMarshalerStateCore(System.Type! type, object? value) -> Java.Interop.JniValueMarshalerState +override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateObjectReferenceArgumentCore(System.Type! type, object? value) -> Java.Interop.JniObjectReference override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> object? override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> T -override Java.Interop.JniRuntime.ReflectionJniValueManager.DestroyValueMarshalerStateCore(ref Java.Interop.JniValueMarshalerState state) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> object? override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> T override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueMarshalerCore(System.Type! type) -> Java.Interop.JniValueMarshaler! From e5f8b41a9cf01d4266ae641736ba95e8cdc7a1a8 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 12 Jun 2026 18:52:41 +0200 Subject: [PATCH 20/20] Clarify local object reference ownership Rename the JavaObjectArray helper hook to CreateLocalObjectReferenceArgument so callers can see that the returned JNI reference is owned and must be disposed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Java.Interop/Java.Interop/JavaObjectArray.cs | 2 +- src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs | 6 +++--- .../Java.Interop/JniRuntime.ReflectionJniValueManager.cs | 2 +- src/Java.Interop/PublicAPI.Unshipped.txt | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JavaObjectArray.cs b/src/Java.Interop/Java.Interop/JavaObjectArray.cs index 0f8ecced2..5e9e439e2 100644 --- a/src/Java.Interop/Java.Interop/JavaObjectArray.cs +++ b/src/Java.Interop/Java.Interop/JavaObjectArray.cs @@ -90,7 +90,7 @@ T GetElementAt (int index) void SetElementAt (int index, T value) { var vm = JniEnvironment.Runtime.ValueManager; - var r = vm.CreateObjectReferenceArgument (typeof (T), value); + var r = vm.CreateLocalObjectReferenceArgument (typeof (T), value); try { JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, index, r); } finally { diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 278654e70..a4b199267 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -312,7 +312,7 @@ protected virtual bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (tr public JniValueMarshaler GetValueMarshaler<[DynamicallyAccessedMembers (Constructors)] T> () => GetValueMarshalerCore (); protected abstract JniValueMarshaler GetValueMarshalerCore<[DynamicallyAccessedMembers (Constructors)] T> (); - internal JniObjectReference CreateObjectReferenceArgument ( + internal JniObjectReference CreateLocalObjectReferenceArgument ( [DynamicallyAccessedMembers (Constructors)] Type type, object? value) @@ -321,10 +321,10 @@ internal JniObjectReference CreateObjectReferenceArgument ( if (type == null) throw new ArgumentNullException (nameof (type)); - return CreateObjectReferenceArgumentCore (type, value); + return CreateLocalObjectReferenceArgumentCore (type, value); } - protected abstract JniObjectReference CreateObjectReferenceArgumentCore ( + protected abstract JniObjectReference CreateLocalObjectReferenceArgumentCore ( [DynamicallyAccessedMembers (Constructors)] Type type, object? value); diff --git a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index faed9853a..7b75d6f7f 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -470,7 +470,7 @@ protected override JniValueMarshaler GetValueMarshalerCore (Type type) return ProxyValueMarshaler.Instance; } - protected override JniObjectReference CreateObjectReferenceArgumentCore ( + protected override JniObjectReference CreateLocalObjectReferenceArgumentCore ( [DynamicallyAccessedMembers (Constructors)] Type type, object? value) diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt index 802cdd08f..84db000b5 100644 --- a/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/src/Java.Interop/PublicAPI.Unshipped.txt @@ -9,7 +9,7 @@ virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeForSimpleReference(string! virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignatureCore(System.Type! type) -> Java.Interop.JniTypeSignature virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignaturesCore(System.Type! type) -> System.Collections.Generic.IEnumerable! static Java.Interop.JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers(Java.Interop.JniType! nativeClass, string! jniSimpleReference, System.ReadOnlySpan methods) -> bool -abstract Java.Interop.JniRuntime.JniValueManager.CreateObjectReferenceArgumentCore(System.Type! type, object? value) -> Java.Interop.JniObjectReference +abstract Java.Interop.JniRuntime.JniValueManager.CreateLocalObjectReferenceArgumentCore(System.Type! type, object? value) -> Java.Interop.JniObjectReference 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.ReflectionJniTypeManager @@ -66,7 +66,7 @@ abstract Java.Interop.JniRuntime.JniValueManager.GetValueMarshalerCore(System.Ty override Java.Interop.JniRuntime.ReflectionJniValueManager.ActivatePeer(Java.Interop.JniObjectReference reference, System.Type! type, System.Reflection.ConstructorInfo! cinfo, object?[]? argumentValues) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.ConstructPeerCore(Java.Interop.IJavaPeerable! peer, ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options) -> void override Java.Interop.JniRuntime.ReflectionJniValueManager.CreatePeer(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, System.Type? targetType) -> Java.Interop.IJavaPeerable? -override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateObjectReferenceArgumentCore(System.Type! type, object? value) -> Java.Interop.JniObjectReference +override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateLocalObjectReferenceArgumentCore(System.Type! type, object? value) -> Java.Interop.JniObjectReference override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> object? override Java.Interop.JniRuntime.ReflectionJniValueManager.CreateValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> T override Java.Interop.JniRuntime.ReflectionJniValueManager.GetValueCore(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type? targetType = null) -> object?