From 7079166f2473d1ff2b41adc8d1bcd56cc8fed4ff Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 28 Nov 2023 13:05:48 -0500 Subject: [PATCH] [Java.Interop] Improve ConstructorInfo lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/xamarin/java.interop/pull/1168#issuecomment-1826386390 > There is a remaining problem with this approach: there is no > requirement of a 1:1 mapping between Java types and managed types. A useful example of that is with arrays: a Java `int[]` array can be treated as one of the following types, in various contexts: * C# `int[]` * `JavaArray` * `JavaPrimitiveArray` * `JavaInt32Array` Update `JavaCallableExample` to demonstrate this: partial class JavaCallableExample { [JavaCallableConstructor(SuperConstructorExpression="")] public JavaCallableExample (int[] a, JavaInt32Array b); } The intention is twofold: 1. This should result in a Java Callable Wrapper constructor with signature `JavaCallableExample(int[] p0, int[] p1)`, and 2. Java code should be able to invoke this constructor. Turns out, neither of these worked when `Type.GetType()` is not used for constructor argument lookup: `JavaCallableWrapperGenerator` didn't fully support e.g. `[JniTypeSignature("I", ArrayRank=1)]`, so didn't know what to do with `JavaInt32Array`. Once (1) was fixed, (2) would fail because `JniRuntime.JniTypeManager.GetType(JniTypeSignature.Parse("[I"))` would return `JavaPrimitiveArray`, which wasn't used in `JavaCallableExample`, resulting in: System.NotSupportedException : Unable to find constructor Java.InteropTests.JavaCallableExample(Java.Interop.JavaPrimitiveArray`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Java.Interop.JavaPrimitiveArray`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]). Please provide the missing constructor. ----> Java.Interop.JniLocationException : Exception of type 'Java.Interop.JniLocationException' was thrown. Stack Trace: at Java.Interop.ManagedPeer.GetConstructor(JniTypeManager typeManager, Type type, String signature, Type[]& parameterTypes) at Java.Interop.ManagedPeer.Construct(IntPtr jnienv, IntPtr klass, IntPtr n_self, IntPtr n_constructorSignature, IntPtr n_constructorArguments) … --- End of managed Java.Interop.JavaException stack trace --- java.lang.Throwable at net.dot.jni.ManagedPeer.construct(Native Method) at net.dot.jni.test.JavaCallableExample.(JavaCallableExample.java:32) at net.dot.jni.test.UseJavaCallableExample.test(UseJavaCallableExample.java:8) Intent (2) had two causes: 1. Using `JniRuntime.JniTypeManager.GetType()` can only return a single type, but there are multiple possible matches. Thus, we need to instead use `JniRuntime.JniTypeManager.GetTypes()`. 2. `JniRuntime.JniTypeManager.GetTypes()` was incomplete, which is a longstanding limitation from f60906cf: for `[I`, it would only return `JavaPrimitiveArray` and `int[]`, in that order. Fix both of these. `JniRuntime.JniTypeManager.GetTypes(JniTypeSignature.Parse("[I"))` will now include: * `JavaArray` * `JavaPrimitiveArray` * `JavaInt32Array` * `int[]` This now allows the `JavaCallableExample` constructor to be invoked from Java. Because `ManagedPeer.Construct()` is now doing so much extra work in order to find the `ConstructorInfo` to invoke, cache the lookups. (Technically this is a "memory leak," as cache entries are never removed.) --- .../JavaCallableWrapperGenerator.cs | 6 + .../JavaNativeTypeManager.cs | 16 ++- .../Java.Interop/JavaPrimitiveArrays.cs | 84 ++++++------ .../Java.Interop/JavaPrimitiveArrays.tt | 42 ++++-- .../Java.Interop/JniRuntime.JniTypeManager.cs | 42 +++++- src/Java.Interop/Java.Interop/ManagedPeer.cs | 128 +++++++++++++----- .../Java.Interop/JniTypeManagerTests.cs | 68 +++++++--- .../Java.Interop/JavaCallableExample.cs | 19 ++- .../Java.Interop/JavaCallableExampleTests.cs | 2 +- .../dot/jni/test/UseJavaCallableExample.java | 4 +- 10 files changed, 288 insertions(+), 123 deletions(-) diff --git a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs index e103e9958..081481c47 100644 --- a/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs +++ b/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs @@ -387,6 +387,12 @@ void AddConstructor (MethodDefinition ctor, TypeDefinition type, string? outerTy } else if (v.Name == "GenerateJavaPeer") { r.DoNotGenerateAcw = ! (bool) v.Argument.Value; } + var isKeyProp = attr.Properties.FirstOrDefault (p => p.Name == "IsKeyword"); + var isKeyword = isKeyProp.Name != null && ((bool) isKeyProp.Argument.Value) == true; + var arrRankProp = attr.Properties.FirstOrDefault (p => p.Name == "ArrayRank"); + if (arrRankProp.Name != null && arrRankProp.Argument.Value is int rank) { + r.Name = new string ('[', rank) + (isKeyword ? r.Name : "L" + r.Name + ";"); + } } return r; } diff --git a/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs b/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs index 176e478a4..5531acc50 100644 --- a/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs +++ b/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs @@ -192,7 +192,7 @@ public static string ToJniName (string jniType, int rank) if (rank == 0) return jniType; - if (jniType.Length > 1) + if (jniType.Length > 1 && jniType [0] != '[') jniType = "L" + jniType + ";"; return new string ('[', rank) + jniType; } @@ -358,7 +358,9 @@ public static int GetArrayInfo (Type type, out Type elementType) if (pJniName == null) { return null; } - return rank == 0 && pJniName.Length > 1 ? "L" + pJniName + ";" : ToJniName (pJniName, rank); + return (rank == 0 && pJniName.Length > 1 && pJniName[0] != '[') + ? "L" + pJniName + ";" + : ToJniName (pJniName, rank); } static ExportParameterKind GetExportKind (System.Reflection.ICustomAttributeProvider p) @@ -556,7 +558,15 @@ public static string ToJniName (TypeDefinition type, IMetadataResolver resolver) var carg = attr.ConstructorArguments.FirstOrDefault (); if (carg.Type == null || carg.Type.FullName != "System.String") return null; - return (string) carg.Value; + var jniType = (string) carg.Value; + var isKeyProp = attr.Properties.FirstOrDefault (p => p.Name == "IsKeyword"); + var isKeyword = isKeyProp.Name != null && ((bool) isKeyProp.Argument.Value) == true; + var arrRankProp = attr.Properties.FirstOrDefault (p => p.Name == "ArrayRank"); + var arrayRank = arrRankProp.Name != null && arrRankProp.Argument.Value is int rank ? rank : 0; + jniType = arrayRank == 0 + ? jniType + : new string ('[', arrayRank) + (isKeyword ? jniType : "L" + jniType + ";"); + return jniType; } static string? ToJniNameFromAttributesForAndroid (TypeDefinition type, IMetadataResolver resolver) diff --git a/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs b/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs index 06bb10f3a..10a41623a 100644 --- a/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs +++ b/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Collections.Generic; @@ -12,50 +12,44 @@ namespace Java.Interop { partial class JniRuntime { - static JniTypeSignature __BooleanTypeArraySignature; - static JniTypeSignature __SByteTypeArraySignature; - static JniTypeSignature __CharTypeArraySignature; - static JniTypeSignature __Int16TypeArraySignature; - static JniTypeSignature __Int32TypeArraySignature; - static JniTypeSignature __Int64TypeArraySignature; - static JniTypeSignature __SingleTypeArraySignature; - static JniTypeSignature __DoubleTypeArraySignature; - - static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature) - { - if (type == typeof (JavaArray) || type == typeof (JavaPrimitiveArray)) { - signature = GetCachedTypeSignature (ref __BooleanTypeArraySignature, "Z", arrayRank: 1, keyword: true); - return true; - } - if (type == typeof (JavaArray) || type == typeof (JavaPrimitiveArray)) { - signature = GetCachedTypeSignature (ref __SByteTypeArraySignature, "B", arrayRank: 1, keyword: true); - return true; - } - if (type == typeof (JavaArray) || type == typeof (JavaPrimitiveArray)) { - signature = GetCachedTypeSignature (ref __CharTypeArraySignature, "C", arrayRank: 1, keyword: true); - return true; - } - if (type == typeof (JavaArray) || type == typeof (JavaPrimitiveArray)) { - signature = GetCachedTypeSignature (ref __Int16TypeArraySignature, "S", arrayRank: 1, keyword: true); - return true; - } - if (type == typeof (JavaArray) || type == typeof (JavaPrimitiveArray)) { - signature = GetCachedTypeSignature (ref __Int32TypeArraySignature, "I", arrayRank: 1, keyword: true); - return true; - } - if (type == typeof (JavaArray) || type == typeof (JavaPrimitiveArray)) { - signature = GetCachedTypeSignature (ref __Int64TypeArraySignature, "J", arrayRank: 1, keyword: true); - return true; - } - if (type == typeof (JavaArray) || type == typeof (JavaPrimitiveArray)) { - signature = GetCachedTypeSignature (ref __SingleTypeArraySignature, "F", arrayRank: 1, keyword: true); - return true; - } - if (type == typeof (JavaArray) || type == typeof (JavaPrimitiveArray)) { - signature = GetCachedTypeSignature (ref __DoubleTypeArraySignature, "D", arrayRank: 1, keyword: true); - return true; - } - return false; + + partial class JniTypeManager { + + readonly struct JniPrimitiveArrayInfo { + public readonly JniTypeSignature JniTypeSignature; + public readonly Type PrimitiveType; + public readonly Type[] ArrayTypes; + + public JniPrimitiveArrayInfo (string jniSimpleReference, Type primitiveType, params Type[] arrayTypes) + { + JniTypeSignature = new JniTypeSignature (jniSimpleReference, arrayRank: 1, keyword: true); + PrimitiveType = primitiveType; + ArrayTypes = arrayTypes; + } + } + + static readonly JniPrimitiveArrayInfo[] JniPrimitiveArrayTypes = new JniPrimitiveArrayInfo[]{ + new ("Z", typeof (Boolean), typeof (Boolean[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaBooleanArray)), + new ("B", typeof (SByte), typeof (SByte[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaSByteArray)), + new ("C", typeof (Char), typeof (Char[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaCharArray)), + new ("S", typeof (Int16), typeof (Int16[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaInt16Array)), + new ("I", typeof (Int32), typeof (Int32[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaInt32Array)), + new ("J", typeof (Int64), typeof (Int64[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaInt64Array)), + new ("F", typeof (Single), typeof (Single[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaSingleArray)), + new ("D", typeof (Double), typeof (Double[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaDoubleArray)), + }; + + static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature) + { + foreach (var e in JniPrimitiveArrayTypes) { + if (Array.IndexOf (e.ArrayTypes, type) < 0) + continue; + signature = e.JniTypeSignature; + return true; + } + signature = default; + return false; + } } static readonly Lazy[]> JniPrimitiveArrayMarshalers = new Lazy[]> (InitJniPrimitiveArrayMarshalers); diff --git a/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt b/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt index d5599c35d..83a3aa160 100644 --- a/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt +++ b/src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt @@ -29,27 +29,43 @@ namespace Java.Interop { }; #> partial class JniRuntime { + + partial class JniTypeManager { + + readonly struct JniPrimitiveArrayInfo { + public readonly JniTypeSignature JniTypeSignature; + public readonly Type PrimitiveType; + public readonly Type[] ArrayTypes; + + public JniPrimitiveArrayInfo (string jniSimpleReference, Type primitiveType, params Type[] arrayTypes) + { + JniTypeSignature = new JniTypeSignature (jniSimpleReference, arrayRank: 1, keyword: true); + PrimitiveType = primitiveType; + ArrayTypes = arrayTypes; + } + } + + static readonly JniPrimitiveArrayInfo[] JniPrimitiveArrayTypes = new JniPrimitiveArrayInfo[]{ <# foreach (var type in arrayTypeInfo) { #> - static JniTypeSignature __<#= type.ManagedType #>TypeArraySignature; + new ("<#= type.JniType #>", typeof (<#= type.ManagedType #>), typeof (<#= type.ManagedType #>[]), typeof (JavaArray<<#= type.ManagedType #>>), typeof (JavaPrimitiveArray<<#= type.ManagedType #>>), typeof (Java<#= type.ManagedType #>Array)), <# } #> + }; - static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature) - { -<# - foreach (var info in arrayTypeInfo) { -#> - if (type == typeof (JavaArray<<#= info.ManagedType #>>) || type == typeof (JavaPrimitiveArray<<#= info.ManagedType #>>)) { - signature = GetCachedTypeSignature (ref __<#= info.ManagedType #>TypeArraySignature, "<#= info.JniType #>", arrayRank: 1, keyword: true); - return true; + static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature) + { + foreach (var e in JniPrimitiveArrayTypes) { + if (Array.IndexOf (e.ArrayTypes, type) < 0) + continue; + signature = e.JniTypeSignature; + return true; + } + signature = default; + return false; } -<# - } -#> - return false; } static readonly Lazy[]> JniPrimitiveArrayMarshalers = new Lazy[]> (InitJniPrimitiveArrayMarshalers); diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs index 616f9ed67..6251e48ee 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs @@ -79,7 +79,7 @@ public override string ToString () } #endif // NET - public class JniTypeManager : IDisposable, ISetRuntime { + public partial class JniTypeManager : IDisposable, ISetRuntime { JniRuntime? runtime; bool disposed; @@ -294,13 +294,16 @@ IEnumerable CreateGetTypesEnumerator (JniTypeSignature typeSignature) continue; } + if (typeSignature.IsKeyword) { + foreach (var t in GetPrimitiveArrayTypesForSimpleReference (typeSignature, type)) { + yield return t; + } + continue; + } + if (typeSignature.ArrayRank > 0) { var rank = typeSignature.ArrayRank; var arrayType = type; - if (typeSignature.IsKeyword) { - arrayType = typeof (JavaPrimitiveArray<>).MakeGenericType (arrayType); - rank--; - } while (rank-- > 0) { arrayType = typeof (JavaObjectArray<>).MakeGenericType (arrayType); } @@ -318,6 +321,35 @@ 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) { + var rank = typeSignature.ArrayRank-1; + var arrayType = t; + while (rank-- > 0) { + arrayType = typeof (JavaObjectArray<>).MakeGenericType (arrayType); + } + yield return arrayType; + + rank = typeSignature.ArrayRank-1; + arrayType = t; + while (rank-- > 0) { + arrayType = arrayType.MakeArrayType (); + } + yield return arrayType; + } + } + protected virtual IEnumerable GetTypesForSimpleReference (string jniSimpleReference) { AssertValid (); diff --git a/src/Java.Interop/Java.Interop/ManagedPeer.cs b/src/Java.Interop/Java.Interop/ManagedPeer.cs index f1b738061..8be86a2b3 100644 --- a/src/Java.Interop/Java.Interop/ManagedPeer.cs +++ b/src/Java.Interop/Java.Interop/ManagedPeer.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -98,12 +99,10 @@ static void Construct ( CreateJniLocationException ()); } - var ptypes = GetParameterTypes (runtime.TypeManager, JniEnvironment.Strings.ToString (n_constructorSignature)); - var pvalues = GetValues (runtime, new JniObjectReference (n_constructorArguments), ptypes); - var cinfo = type.GetConstructor (ptypes); - if (cinfo == null) { - throw CreateMissingConstructorException (type, ptypes); - } + var ctorSig = JniEnvironment.Strings.ToString (n_constructorSignature) ?? "()V"; + var cinfo = GetConstructor (type, typeSig.SimpleReference, ctorSig) ?? + throw CreateMissingConstructorException (type, ctorSig); + var pvalues = GetValues (runtime, new JniObjectReference (n_constructorArguments), cinfo); if (self != null) { cinfo.Invoke (self, pvalues); @@ -120,26 +119,18 @@ static void Construct ( } } - static Exception CreateMissingConstructorException (Type type, Type [] ptypes) + static Exception CreateMissingConstructorException (Type type, string signature) { var message = new StringBuilder (); - message.Append ("Unable to find constructor "); + message.Append ("Unable to find constructor for type `"); message.Append (type.FullName); - message.Append ("("); - - if (ptypes.Length > 0) { - message.Append (ptypes [0].FullName); - for (int i = 1; i < ptypes.Length; ++i) - message.Append (", ").Append (ptypes [i].FullName); - } - - message.Append (")"); - message.Append (". Please provide the missing constructor."); + message.Append ("` with JNI signature `"); + message.Append (signature); + message.Append ("`. Please provide the missing constructor."); return new NotSupportedException (message.ToString (), CreateJniLocationException ()); } - static Exception CreateJniLocationException () { using (var e = new JavaException ()) { @@ -147,30 +138,103 @@ static Exception CreateJniLocationException () } } - static Type[] GetParameterTypes (JniRuntime.JniTypeManager typeMgr, string? signature) + static Dictionary ConstructorCache = new Dictionary (); + + static ConstructorInfo? GetConstructor (Type type, string jniTypeName, string signature) + { + var ctorCacheKey = jniTypeName + "." + signature; + lock (ConstructorCache) { + if (ConstructorCache.TryGetValue (ctorCacheKey, out var ctor)) { + return ctor; + } + } + + var candidateParameterTypes = GetConstructorCandidateParameterTypes (signature); + if (candidateParameterTypes.Length == 0) { + return CacheConstructor (ctorCacheKey, type.GetConstructor (Array.Empty ())); + } + + var constructors = new List(type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)); + + // Filter out wrong parameter count + for (int c = constructors.Count; c > 0; --c) { + if (constructors [c-1].GetParameters ().Length != candidateParameterTypes.Length) { + constructors.RemoveAt (c-1); + } + } + + // Filter out mismatched types + for (int c = constructors.Count; c > 0; --c) { + var parameters = constructors [c-1].GetParameters (); + for (int i = 0; i < parameters.Length; ++i) { + if (!candidateParameterTypes [i].Contains (parameters [i].ParameterType)) { + constructors.RemoveAt (c-1); + break; + } + } + } + + if (constructors.Count == 0) + return CacheConstructor (ctorCacheKey, null); + + if (constructors.Count != 1) { + var message = new StringBuilder ($"Found {constructors.Count} constructors matching JNI signature {signature}:") + .Append (Environment.NewLine); + foreach (var c in constructors) { + message.Append (" ").Append (c).Append (Environment.NewLine); + } + throw new NotSupportedException (message.ToString (), CreateJniLocationException ()); + } + + return CacheConstructor (ctorCacheKey, constructors [0]); + } + + static ConstructorInfo? CacheConstructor (string cacheKey, ConstructorInfo? ctor) + { + lock (ConstructorCache) { + if (ConstructorCache.TryGetValue (cacheKey, out var existing)) { + return existing; + } + ConstructorCache.Add (cacheKey, ctor); + } + return ctor; + } + + static List[] GetConstructorCandidateParameterTypes (string signature) { - if (string.IsNullOrEmpty (signature)) - return Array.Empty (); - var ptypes = new Type [JniMemberSignature.GetParameterCountFromMethodSignature (signature)]; - int i = 0; + var parameterCount = JniMemberSignature.GetParameterCountFromMethodSignature (signature); + if (parameterCount == 0) { + return Array.Empty> (); + } + var typeManager = JniEnvironment.Runtime.TypeManager; + var candidateParameterTypes = new List[parameterCount]; + int i = 0; foreach (var jniType in JniMemberSignature.GetParameterTypesFromMethodSignature (signature)) { - ptypes [i++] = GetTypeFromSignature (typeMgr, jniType, signature); + var possibleTypes = new List (typeManager.GetTypes (jniType)); + if (possibleTypes.Count == 0) { + throw new NotSupportedException ( + $"Could not find System.Type corresponding to Java type `{jniType}` within constructor signature `{signature}`.", + CreateJniLocationException ()); + } + candidateParameterTypes [i++] = possibleTypes; } - return ptypes; + return candidateParameterTypes; } - static object?[]? GetValues (JniRuntime runtime, JniObjectReference values, Type[] types) + static object?[]? GetValues (JniRuntime runtime, JniObjectReference values, ConstructorInfo cinfo) { if (!values.IsValid) return null; + var parameters = cinfo.GetParameters (); + int len = JniEnvironment.Arrays.GetArrayLength (values); - Debug.Assert (len == types.Length, - string.Format ("Unexpected number of parameter types! Expected {0}, got {1}", types.Length, len)); - var pvalues = new object? [types.Length]; - for (int i = 0; i < pvalues.Length; ++i) { + Debug.Assert (len == parameters.Length, + $"Unexpected number of parameter types! Expected {parameters.Length}, got {len}"); + var pvalues = new object? [len]; + for (int i = 0; i < len; ++i) { var n_value = JniEnvironment.Arrays.GetObjectArrayElement (values, i); - var value = runtime.ValueManager.GetValue (ref n_value, JniObjectReferenceOptions.CopyAndDispose, types [i]); + var value = runtime.ValueManager.GetValue (ref n_value, JniObjectReferenceOptions.CopyAndDispose, parameters [i].ParameterType); pvalues [i] = value; } diff --git a/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs index 53705805f..c6be06530 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Java.Interop; @@ -115,25 +117,25 @@ static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeywo Assert.AreEqual (null, GetType ("Lcom/example/does/not/exist;")); Assert.AreEqual (null, GetType ("[Lcom/example/does/not/exist;")); - Assert.AreEqual (typeof (JavaPrimitiveArray), GetType ("[Z")); - Assert.AreEqual (typeof (JavaPrimitiveArray), GetType ("[C")); - Assert.AreEqual (typeof (JavaPrimitiveArray), GetType ("[B")); - Assert.AreEqual (typeof (JavaPrimitiveArray), GetType ("[S")); - Assert.AreEqual (typeof (JavaPrimitiveArray), GetType ("[I")); - Assert.AreEqual (typeof (JavaPrimitiveArray), GetType ("[J")); - Assert.AreEqual (typeof (JavaPrimitiveArray), GetType ("[F")); - Assert.AreEqual (typeof (JavaPrimitiveArray), GetType ("[D")); - Assert.AreEqual (typeof (JavaObjectArray), GetType ("[Ljava/lang/String;")); - - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[Z")); - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[C")); - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[B")); - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[S")); - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[I")); - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[J")); - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[F")); - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[D")); - Assert.AreEqual (typeof (JavaObjectArray>), GetType ("[[Ljava/lang/String;")); + AssertPrimitiveArrayTypesFromSignature (manager, "[Z", typeof (JavaBooleanArray)); + AssertPrimitiveArrayTypesFromSignature (manager, "[C", typeof (JavaCharArray)); + AssertPrimitiveArrayTypesFromSignature (manager, "[B", typeof (JavaSByteArray)); + AssertPrimitiveArrayTypesFromSignature (manager, "[S", typeof (JavaInt16Array)); + AssertPrimitiveArrayTypesFromSignature (manager, "[I", typeof (JavaInt32Array)); + AssertPrimitiveArrayTypesFromSignature (manager, "[J", typeof (JavaInt64Array)); + AssertPrimitiveArrayTypesFromSignature (manager, "[F", typeof (JavaSingleArray)); + AssertPrimitiveArrayTypesFromSignature (manager, "[D", typeof (JavaDoubleArray)); + AssertArrayTypesFromSignature (manager, "[Ljava/lang/String;", typeof (JavaObjectArray)); + + AssertArrayTypesFromSignature (manager, "[[Z", typeof (JavaObjectArray>)); + AssertArrayTypesFromSignature (manager, "[[C", typeof (JavaObjectArray>)); + AssertArrayTypesFromSignature (manager, "[[B", typeof (JavaObjectArray>)); + AssertArrayTypesFromSignature (manager, "[[S", typeof (JavaObjectArray>)); + AssertArrayTypesFromSignature (manager, "[[I", typeof (JavaObjectArray>)); + AssertArrayTypesFromSignature (manager, "[[J", typeof (JavaObjectArray>)); + AssertArrayTypesFromSignature (manager, "[[F", typeof (JavaObjectArray>)); + AssertArrayTypesFromSignature (manager, "[[D", typeof (JavaObjectArray>)); + AssertArrayTypesFromSignature (manager, "[[Ljava/lang/String;", typeof (JavaObjectArray>)); // Yes, these look weird... // Assume: class II {} @@ -142,6 +144,34 @@ static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeywo Assert.AreEqual (null, GetType ("Ljava/lang/String")); } + static void AssertPrimitiveArrayTypesFromSignature (JniRuntime.JniTypeManager manager, string signature, params Type[] expectedTypes) + { + var sig = JniTypeSignature.Parse (signature); + var types = manager.GetTypes (sig).ToList (); + var messageFormat = $"Types for signature `{signature}` should contain `{{0}}`, instead contains: {string.Join (", ", types)}"; + var arrayTypes = new[]{ + typeof (JavaArray), + typeof (JavaPrimitiveArray), + typeof (T[]), + }.Concat (expectedTypes); + foreach (var t in arrayTypes) { + Assert.IsTrue (types.Contains (t), string.Format (messageFormat, t)); + } + } + + static void AssertArrayTypesFromSignature (JniRuntime.JniTypeManager manager, string signature, params Type[] expectedTypes) + { + var sig = JniTypeSignature.Parse (signature); + var types = manager.GetTypes (sig).ToList (); + var messageFormat = $"Types for signature `{signature}` should contain `{{0}}`, instead contains: {string.Join (", ", types)}"; + var arrayTypes = new[]{ + typeof (T[]), + }.Concat (expectedTypes); + foreach (var t in arrayTypes) { + Assert.IsTrue (types.Contains (t), string.Format (messageFormat, t)); + } + } + [Test] public void GetTypeSignature () { diff --git a/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExample.cs b/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExample.cs index 4b6362dfc..ae8bfdea5 100644 --- a/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExample.cs +++ b/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExample.cs @@ -1,3 +1,6 @@ +using System; +using System.Text; + using Java.Interop; namespace Java.InteropTests; @@ -8,15 +11,25 @@ class JavaCallableExample : Java.Lang.Object { internal const string TypeSignature = "net/dot/jni/test/JavaCallableExample"; [JavaCallableConstructor(SuperConstructorExpression="")] - public JavaCallableExample (int a) + public JavaCallableExample (int[] a, JavaInt32Array b) { this.a = a; + this.b = b; + + var bDescription = new StringBuilder (); + for (int i = 0; i < b.Length; ++i) { + if (i > 0) + bDescription.Append (", "); + bDescription.Append (b [i]); + } + Console.WriteLine ($"JavaCallableExample ({{{string.Join (", ", a)}}}, {{{bDescription}}})"); } - int a; + int[] a; + JavaInt32Array b; [JavaCallable ("getA")] - public int GetA () + public int[] GetA () { return a; } diff --git a/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExampleTests.cs b/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExampleTests.cs index 52efcfb22..99e3e5aa7 100644 --- a/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExampleTests.cs +++ b/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExampleTests.cs @@ -19,7 +19,7 @@ public void JavaCallableWrappers () [Test] public void ManagedCtorInvokesJavaDefaultCtor () { - using var o = new JavaCallableExample (42); + using var o = new JavaCallableExample (new[]{1,2}, new JavaInt32Array (new[]{3,4})); } static JniType CreateUseJavaCallableExampleType () => diff --git a/tests/Java.Interop.Export-Tests/java/net/dot/jni/test/UseJavaCallableExample.java b/tests/Java.Interop.Export-Tests/java/net/dot/jni/test/UseJavaCallableExample.java index 780daf4c8..e6ec1eb5d 100644 --- a/tests/Java.Interop.Export-Tests/java/net/dot/jni/test/UseJavaCallableExample.java +++ b/tests/Java.Interop.Export-Tests/java/net/dot/jni/test/UseJavaCallableExample.java @@ -5,7 +5,7 @@ public class UseJavaCallableExample { public static boolean test() { - JavaCallableExample e = new JavaCallableExample(42); - return e.getA() == 42; + JavaCallableExample e = new JavaCallableExample(new int[]{1,2}, new int[]{3, 4}); + return e.getA()[0] == 1; } }