Skip to content

Commit 7079166

Browse files
committed
[Java.Interop] Improve ConstructorInfo lookup
Context: #1168 (comment) > 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<int>` * `JavaPrimitiveArray<int>` * `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<int>`, 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.<init>(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 f60906c: for `[I`, it would only return `JavaPrimitiveArray<int>` and `int[]`, in that order. Fix both of these. `JniRuntime.JniTypeManager.GetTypes(JniTypeSignature.Parse("[I"))` will now include: * `JavaArray<int>` * `JavaPrimitiveArray<int>` * `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.)
1 parent b75ad19 commit 7079166

File tree

10 files changed

+288
-123
lines changed

10 files changed

+288
-123
lines changed

src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGenerator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,12 @@ void AddConstructor (MethodDefinition ctor, TypeDefinition type, string? outerTy
387387
} else if (v.Name == "GenerateJavaPeer") {
388388
r.DoNotGenerateAcw = ! (bool) v.Argument.Value;
389389
}
390+
var isKeyProp = attr.Properties.FirstOrDefault (p => p.Name == "IsKeyword");
391+
var isKeyword = isKeyProp.Name != null && ((bool) isKeyProp.Argument.Value) == true;
392+
var arrRankProp = attr.Properties.FirstOrDefault (p => p.Name == "ArrayRank");
393+
if (arrRankProp.Name != null && arrRankProp.Argument.Value is int rank) {
394+
r.Name = new string ('[', rank) + (isKeyword ? r.Name : "L" + r.Name + ";");
395+
}
390396
}
391397
return r;
392398
}

src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public static string ToJniName (string jniType, int rank)
192192
if (rank == 0)
193193
return jniType;
194194

195-
if (jniType.Length > 1)
195+
if (jniType.Length > 1 && jniType [0] != '[')
196196
jniType = "L" + jniType + ";";
197197
return new string ('[', rank) + jniType;
198198
}
@@ -358,7 +358,9 @@ public static int GetArrayInfo (Type type, out Type elementType)
358358
if (pJniName == null) {
359359
return null;
360360
}
361-
return rank == 0 && pJniName.Length > 1 ? "L" + pJniName + ";" : ToJniName (pJniName, rank);
361+
return (rank == 0 && pJniName.Length > 1 && pJniName[0] != '[')
362+
? "L" + pJniName + ";"
363+
: ToJniName (pJniName, rank);
362364
}
363365

364366
static ExportParameterKind GetExportKind (System.Reflection.ICustomAttributeProvider p)
@@ -556,7 +558,15 @@ public static string ToJniName (TypeDefinition type, IMetadataResolver resolver)
556558
var carg = attr.ConstructorArguments.FirstOrDefault ();
557559
if (carg.Type == null || carg.Type.FullName != "System.String")
558560
return null;
559-
return (string) carg.Value;
561+
var jniType = (string) carg.Value;
562+
var isKeyProp = attr.Properties.FirstOrDefault (p => p.Name == "IsKeyword");
563+
var isKeyword = isKeyProp.Name != null && ((bool) isKeyProp.Argument.Value) == true;
564+
var arrRankProp = attr.Properties.FirstOrDefault (p => p.Name == "ArrayRank");
565+
var arrayRank = arrRankProp.Name != null && arrRankProp.Argument.Value is int rank ? rank : 0;
566+
jniType = arrayRank == 0
567+
? jniType
568+
: new string ('[', arrayRank) + (isKeyword ? jniType : "L" + jniType + ";");
569+
return jniType;
560570
}
561571

562572
static string? ToJniNameFromAttributesForAndroid (TypeDefinition type, IMetadataResolver resolver)

src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#nullable enable
1+
#nullable enable
22

33
using System;
44
using System.Collections.Generic;
@@ -12,50 +12,44 @@
1212
namespace Java.Interop {
1313

1414
partial class JniRuntime {
15-
static JniTypeSignature __BooleanTypeArraySignature;
16-
static JniTypeSignature __SByteTypeArraySignature;
17-
static JniTypeSignature __CharTypeArraySignature;
18-
static JniTypeSignature __Int16TypeArraySignature;
19-
static JniTypeSignature __Int32TypeArraySignature;
20-
static JniTypeSignature __Int64TypeArraySignature;
21-
static JniTypeSignature __SingleTypeArraySignature;
22-
static JniTypeSignature __DoubleTypeArraySignature;
23-
24-
static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature)
25-
{
26-
if (type == typeof (JavaArray<Boolean>) || type == typeof (JavaPrimitiveArray<Boolean>)) {
27-
signature = GetCachedTypeSignature (ref __BooleanTypeArraySignature, "Z", arrayRank: 1, keyword: true);
28-
return true;
29-
}
30-
if (type == typeof (JavaArray<SByte>) || type == typeof (JavaPrimitiveArray<SByte>)) {
31-
signature = GetCachedTypeSignature (ref __SByteTypeArraySignature, "B", arrayRank: 1, keyword: true);
32-
return true;
33-
}
34-
if (type == typeof (JavaArray<Char>) || type == typeof (JavaPrimitiveArray<Char>)) {
35-
signature = GetCachedTypeSignature (ref __CharTypeArraySignature, "C", arrayRank: 1, keyword: true);
36-
return true;
37-
}
38-
if (type == typeof (JavaArray<Int16>) || type == typeof (JavaPrimitiveArray<Int16>)) {
39-
signature = GetCachedTypeSignature (ref __Int16TypeArraySignature, "S", arrayRank: 1, keyword: true);
40-
return true;
41-
}
42-
if (type == typeof (JavaArray<Int32>) || type == typeof (JavaPrimitiveArray<Int32>)) {
43-
signature = GetCachedTypeSignature (ref __Int32TypeArraySignature, "I", arrayRank: 1, keyword: true);
44-
return true;
45-
}
46-
if (type == typeof (JavaArray<Int64>) || type == typeof (JavaPrimitiveArray<Int64>)) {
47-
signature = GetCachedTypeSignature (ref __Int64TypeArraySignature, "J", arrayRank: 1, keyword: true);
48-
return true;
49-
}
50-
if (type == typeof (JavaArray<Single>) || type == typeof (JavaPrimitiveArray<Single>)) {
51-
signature = GetCachedTypeSignature (ref __SingleTypeArraySignature, "F", arrayRank: 1, keyword: true);
52-
return true;
53-
}
54-
if (type == typeof (JavaArray<Double>) || type == typeof (JavaPrimitiveArray<Double>)) {
55-
signature = GetCachedTypeSignature (ref __DoubleTypeArraySignature, "D", arrayRank: 1, keyword: true);
56-
return true;
57-
}
58-
return false;
15+
16+
partial class JniTypeManager {
17+
18+
readonly struct JniPrimitiveArrayInfo {
19+
public readonly JniTypeSignature JniTypeSignature;
20+
public readonly Type PrimitiveType;
21+
public readonly Type[] ArrayTypes;
22+
23+
public JniPrimitiveArrayInfo (string jniSimpleReference, Type primitiveType, params Type[] arrayTypes)
24+
{
25+
JniTypeSignature = new JniTypeSignature (jniSimpleReference, arrayRank: 1, keyword: true);
26+
PrimitiveType = primitiveType;
27+
ArrayTypes = arrayTypes;
28+
}
29+
}
30+
31+
static readonly JniPrimitiveArrayInfo[] JniPrimitiveArrayTypes = new JniPrimitiveArrayInfo[]{
32+
new ("Z", typeof (Boolean), typeof (Boolean[]), typeof (JavaArray<Boolean>), typeof (JavaPrimitiveArray<Boolean>), typeof (JavaBooleanArray)),
33+
new ("B", typeof (SByte), typeof (SByte[]), typeof (JavaArray<SByte>), typeof (JavaPrimitiveArray<SByte>), typeof (JavaSByteArray)),
34+
new ("C", typeof (Char), typeof (Char[]), typeof (JavaArray<Char>), typeof (JavaPrimitiveArray<Char>), typeof (JavaCharArray)),
35+
new ("S", typeof (Int16), typeof (Int16[]), typeof (JavaArray<Int16>), typeof (JavaPrimitiveArray<Int16>), typeof (JavaInt16Array)),
36+
new ("I", typeof (Int32), typeof (Int32[]), typeof (JavaArray<Int32>), typeof (JavaPrimitiveArray<Int32>), typeof (JavaInt32Array)),
37+
new ("J", typeof (Int64), typeof (Int64[]), typeof (JavaArray<Int64>), typeof (JavaPrimitiveArray<Int64>), typeof (JavaInt64Array)),
38+
new ("F", typeof (Single), typeof (Single[]), typeof (JavaArray<Single>), typeof (JavaPrimitiveArray<Single>), typeof (JavaSingleArray)),
39+
new ("D", typeof (Double), typeof (Double[]), typeof (JavaArray<Double>), typeof (JavaPrimitiveArray<Double>), typeof (JavaDoubleArray)),
40+
};
41+
42+
static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature)
43+
{
44+
foreach (var e in JniPrimitiveArrayTypes) {
45+
if (Array.IndexOf (e.ArrayTypes, type) < 0)
46+
continue;
47+
signature = e.JniTypeSignature;
48+
return true;
49+
}
50+
signature = default;
51+
return false;
52+
}
5953
}
6054

6155
static readonly Lazy<KeyValuePair<Type, JniValueMarshaler>[]> JniPrimitiveArrayMarshalers = new Lazy<KeyValuePair<Type, JniValueMarshaler>[]> (InitJniPrimitiveArrayMarshalers);

src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,43 @@ namespace Java.Interop {
2929
};
3030
#>
3131
partial class JniRuntime {
32+
33+
partial class JniTypeManager {
34+
35+
readonly struct JniPrimitiveArrayInfo {
36+
public readonly JniTypeSignature JniTypeSignature;
37+
public readonly Type PrimitiveType;
38+
public readonly Type[] ArrayTypes;
39+
40+
public JniPrimitiveArrayInfo (string jniSimpleReference, Type primitiveType, params Type[] arrayTypes)
41+
{
42+
JniTypeSignature = new JniTypeSignature (jniSimpleReference, arrayRank: 1, keyword: true);
43+
PrimitiveType = primitiveType;
44+
ArrayTypes = arrayTypes;
45+
}
46+
}
47+
48+
static readonly JniPrimitiveArrayInfo[] JniPrimitiveArrayTypes = new JniPrimitiveArrayInfo[]{
3249
<#
3350
foreach (var type in arrayTypeInfo) {
3451
#>
35-
static JniTypeSignature __<#= type.ManagedType #>TypeArraySignature;
52+
new ("<#= type.JniType #>", typeof (<#= type.ManagedType #>), typeof (<#= type.ManagedType #>[]), typeof (JavaArray<<#= type.ManagedType #>>), typeof (JavaPrimitiveArray<<#= type.ManagedType #>>), typeof (Java<#= type.ManagedType #>Array)),
3653
<#
3754
}
3855
#>
56+
};
3957

40-
static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature)
41-
{
42-
<#
43-
foreach (var info in arrayTypeInfo) {
44-
#>
45-
if (type == typeof (JavaArray<<#= info.ManagedType #>>) || type == typeof (JavaPrimitiveArray<<#= info.ManagedType #>>)) {
46-
signature = GetCachedTypeSignature (ref __<#= info.ManagedType #>TypeArraySignature, "<#= info.JniType #>", arrayRank: 1, keyword: true);
47-
return true;
58+
static bool GetBuiltInTypeArraySignature (Type type, ref JniTypeSignature signature)
59+
{
60+
foreach (var e in JniPrimitiveArrayTypes) {
61+
if (Array.IndexOf (e.ArrayTypes, type) < 0)
62+
continue;
63+
signature = e.JniTypeSignature;
64+
return true;
65+
}
66+
signature = default;
67+
return false;
4868
}
49-
<#
50-
}
51-
#>
52-
return false;
5369
}
5470

5571
static readonly Lazy<KeyValuePair<Type, JniValueMarshaler>[]> JniPrimitiveArrayMarshalers = new Lazy<KeyValuePair<Type, JniValueMarshaler>[]> (InitJniPrimitiveArrayMarshalers);

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

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public override string ToString ()
7979
}
8080
#endif // NET
8181

82-
public class JniTypeManager : IDisposable, ISetRuntime {
82+
public partial class JniTypeManager : IDisposable, ISetRuntime {
8383

8484
JniRuntime? runtime;
8585
bool disposed;
@@ -294,13 +294,16 @@ IEnumerable<Type> CreateGetTypesEnumerator (JniTypeSignature typeSignature)
294294
continue;
295295
}
296296

297+
if (typeSignature.IsKeyword) {
298+
foreach (var t in GetPrimitiveArrayTypesForSimpleReference (typeSignature, type)) {
299+
yield return t;
300+
}
301+
continue;
302+
}
303+
297304
if (typeSignature.ArrayRank > 0) {
298305
var rank = typeSignature.ArrayRank;
299306
var arrayType = type;
300-
if (typeSignature.IsKeyword) {
301-
arrayType = typeof (JavaPrimitiveArray<>).MakeGenericType (arrayType);
302-
rank--;
303-
}
304307
while (rank-- > 0) {
305308
arrayType = typeof (JavaObjectArray<>).MakeGenericType (arrayType);
306309
}
@@ -318,6 +321,35 @@ IEnumerable<Type> CreateGetTypesEnumerator (JniTypeSignature typeSignature)
318321
}
319322
}
320323

324+
IEnumerable<Type> GetPrimitiveArrayTypesForSimpleReference (JniTypeSignature typeSignature, Type type)
325+
{
326+
int index = -1;
327+
for (int i = 0; i < JniPrimitiveArrayTypes.Length; ++i) {
328+
if (JniPrimitiveArrayTypes [i].PrimitiveType == type) {
329+
index = i;
330+
break;
331+
}
332+
}
333+
if (index == -1) {
334+
throw new InvalidOperationException ($"Should not be reached; Could not find JniPrimitiveArrayInfo for {type}");
335+
}
336+
foreach (var t in JniPrimitiveArrayTypes [index].ArrayTypes) {
337+
var rank = typeSignature.ArrayRank-1;
338+
var arrayType = t;
339+
while (rank-- > 0) {
340+
arrayType = typeof (JavaObjectArray<>).MakeGenericType (arrayType);
341+
}
342+
yield return arrayType;
343+
344+
rank = typeSignature.ArrayRank-1;
345+
arrayType = t;
346+
while (rank-- > 0) {
347+
arrayType = arrayType.MakeArrayType ();
348+
}
349+
yield return arrayType;
350+
}
351+
}
352+
321353
protected virtual IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
322354
{
323355
AssertValid ();

0 commit comments

Comments
 (0)