Skip to content

Commit

Permalink
[Java.Interop] Improve ConstructorInfo lookup
Browse files Browse the repository at this point in the history
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.)
  • Loading branch information
jonpryor committed Nov 28, 2023
1 parent b75ad19 commit 7079166
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
84 changes: 39 additions & 45 deletions src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#nullable enable
#nullable enable

using System;
using System.Collections.Generic;
Expand All @@ -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<Boolean>) || type == typeof (JavaPrimitiveArray<Boolean>)) {
signature = GetCachedTypeSignature (ref __BooleanTypeArraySignature, "Z", arrayRank: 1, keyword: true);
return true;
}
if (type == typeof (JavaArray<SByte>) || type == typeof (JavaPrimitiveArray<SByte>)) {
signature = GetCachedTypeSignature (ref __SByteTypeArraySignature, "B", arrayRank: 1, keyword: true);
return true;
}
if (type == typeof (JavaArray<Char>) || type == typeof (JavaPrimitiveArray<Char>)) {
signature = GetCachedTypeSignature (ref __CharTypeArraySignature, "C", arrayRank: 1, keyword: true);
return true;
}
if (type == typeof (JavaArray<Int16>) || type == typeof (JavaPrimitiveArray<Int16>)) {
signature = GetCachedTypeSignature (ref __Int16TypeArraySignature, "S", arrayRank: 1, keyword: true);
return true;
}
if (type == typeof (JavaArray<Int32>) || type == typeof (JavaPrimitiveArray<Int32>)) {
signature = GetCachedTypeSignature (ref __Int32TypeArraySignature, "I", arrayRank: 1, keyword: true);
return true;
}
if (type == typeof (JavaArray<Int64>) || type == typeof (JavaPrimitiveArray<Int64>)) {
signature = GetCachedTypeSignature (ref __Int64TypeArraySignature, "J", arrayRank: 1, keyword: true);
return true;
}
if (type == typeof (JavaArray<Single>) || type == typeof (JavaPrimitiveArray<Single>)) {
signature = GetCachedTypeSignature (ref __SingleTypeArraySignature, "F", arrayRank: 1, keyword: true);
return true;
}
if (type == typeof (JavaArray<Double>) || type == typeof (JavaPrimitiveArray<Double>)) {
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<Boolean>), typeof (JavaPrimitiveArray<Boolean>), typeof (JavaBooleanArray)),
new ("B", typeof (SByte), typeof (SByte[]), typeof (JavaArray<SByte>), typeof (JavaPrimitiveArray<SByte>), typeof (JavaSByteArray)),
new ("C", typeof (Char), typeof (Char[]), typeof (JavaArray<Char>), typeof (JavaPrimitiveArray<Char>), typeof (JavaCharArray)),
new ("S", typeof (Int16), typeof (Int16[]), typeof (JavaArray<Int16>), typeof (JavaPrimitiveArray<Int16>), typeof (JavaInt16Array)),
new ("I", typeof (Int32), typeof (Int32[]), typeof (JavaArray<Int32>), typeof (JavaPrimitiveArray<Int32>), typeof (JavaInt32Array)),
new ("J", typeof (Int64), typeof (Int64[]), typeof (JavaArray<Int64>), typeof (JavaPrimitiveArray<Int64>), typeof (JavaInt64Array)),
new ("F", typeof (Single), typeof (Single[]), typeof (JavaArray<Single>), typeof (JavaPrimitiveArray<Single>), typeof (JavaSingleArray)),
new ("D", typeof (Double), typeof (Double[]), typeof (JavaArray<Double>), typeof (JavaPrimitiveArray<Double>), 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<KeyValuePair<Type, JniValueMarshaler>[]> JniPrimitiveArrayMarshalers = new Lazy<KeyValuePair<Type, JniValueMarshaler>[]> (InitJniPrimitiveArrayMarshalers);
Expand Down
42 changes: 29 additions & 13 deletions src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt
Original file line number Diff line number Diff line change
Expand Up @@ -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<KeyValuePair<Type, JniValueMarshaler>[]> JniPrimitiveArrayMarshalers = new Lazy<KeyValuePair<Type, JniValueMarshaler>[]> (InitJniPrimitiveArrayMarshalers);
Expand Down
42 changes: 37 additions & 5 deletions src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -294,13 +294,16 @@ IEnumerable<Type> 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);
}
Expand All @@ -318,6 +321,35 @@ IEnumerable<Type> CreateGetTypesEnumerator (JniTypeSignature typeSignature)
}
}

IEnumerable<Type> 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<Type> GetTypesForSimpleReference (string jniSimpleReference)
{
AssertValid ();
Expand Down
Loading

0 comments on commit 7079166

Please sign in to comment.