Skip to content

Commit

Permalink
[Java.Interop] Avoid Type.GetType() in ManagedPeer (#1168)
Browse files Browse the repository at this point in the history
Fixes: #1165

Context: #1153
Context: #1157
Context: f60906c

When building for NativeAOT (#1153) or when building .NET Android
apps with `-p:IsAotcompatible=true` (#1157), we get [IL2057][0]
warnings from `ManagedPeer.cs`:

	ManagedPeer.cs(93,19,93,112): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.
	ManagedPeer.cs(156,18,156,65): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.
	ManagedPeer.cs(198,35,198,92): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.

These warnings are because `ManagedPeer.Construct()` and
`ManagedPeer.RegisterNativeMembers()` use `Type.GetType()` on string
values provided *from Java code*, and thus the IL trimmer does not
have visibility into those strings, and thus cannot reliably
determine which types need to be preserved:

	// Java Callable Wrapper
	/* partial */ class ManagedType
	{
	  public static final String __md_methods;
	  static {
	    __md_methods =
	      "n_GetString:()Ljava/lang/String;:__export__\n" +
	      "";
	    net.dot.jni.ManagedPeer.registerNativeMembers (
	        /* nativeClass */             ManagedType.class,
	        /* assemblyQualifiedName */   "Example.ManagedType, Hello-NativeAOTFromJNI",
	        /* methods */                 __md_methods);
	  }

	  public ManagedType (int p0)
	  {
	    super ();
	    if (getClass () == ManagedType.class) {
	      net.dot.jni.ManagedPeer.construct (
	          /* self */                  this,
	          /* assemblyQualifiedName */ "Example.ManagedType, Hello-NativeAOTFromJNI",
	          /* constructorSignature */  "System.Int32, System.Runtime",
	          /* arguments */             new java.lang.Object[] { p0 });
	    }
	  }
	}

`ManagedPeer.construct()` passes *two* sets of assembly-qualified
type names: `assemblyQualifiedName` contains the type to construct,
while `constructorSignature` contains a `:`-separated list of
assembly-qualified type names for the constructor parameters.
Each of these are passed to `Type.GetType()`.

`ManagedPeer.registerNativeMembers()` passes an assembly-qualified
type name to `ManagedPeer.RegisterNativeMembers()`, which passes the
assembly-qualified type name to `Type.GetType()` to find the type
to register native methods for.

If we more strongly rely on JNI signatures, we can remove the need
for Java Callable Wrappers to contain assembly-qualified type names
entirely, thus removing the need for `ManagedPeer` to use
`Type.GetType()`, removing the IL2057 warnings.

For `ManagedPeer.construct()`, `assemblyQualifiedName` can be
replaced with getting the JNI type signature from `self.getClass()`,
and `constructorSignature` can be replaced with a
*JNI method signature* of the calling constructor.

For `ManagedPeer.registerNativeMembers()`, `assemblyQualifiedName`
can be replaced with getting the JNI type signature from `nativeClass`.
`jcw-gen --codegen-target=JavaInterop1` output becomes:

	// New JavaInterop1 Java Callable Wrapper
	/* partial */ class ManagedType
	{
	  public static final String __md_methods;
	  static {
	    __md_methods =
	      "n_GetString:()Ljava/lang/String;:__export__\n" +
	      "";
	    net.dot.jni.ManagedPeer.registerNativeMembers (
	        /* nativeClass */             ManagedType.class,
	        /* methods */                 __md_methods);
	  }

	  public ManagedType (int p0)
	  {
	    super ();
	    if (getClass () == ManagedType.class) {
	      net.dot.jni.ManagedPeer.construct (
	          /* self */                  this,
	          /* constructorSignature */  "(I)V",
	          /* arguments */             new java.lang.Object[] { p0 });
	    }
	  }
	}

This does not alter `jcw-gen --codegen-target=XAJavaInterop1` output;
.NET Android will continue to require `Type.GetType()` calls within
xamarin/xamarin-android, e.g.
[`AndroidTypeManager.RegisterNativeMembers()`][2].

Furthermore, if we add `[DynamicallyAccessedMembers]` to
`JniRuntime.JniTypeManager.GetType()`, we can fix some [IL2075][1]
warnings which appeared after fixing the IL2057 warnings.

Aside: Excising assembly-qualified type names from Java Callable
Wrappers had some "interesting" knock-on effects in the unit tests,
requiring that more typemap information be explicitly provided.
(This same information was *implicitly* provided before, via the
provision of assembly-qualified type names everywhere…)

One problem with the approach of using JNI signatures instead of
using assembly-qualified names is *ambiguity*: there can be multiple
managed types which correspond to a given JNI signature.  Consider
the JNI signature `[I`, which is a Java `int[]`.  This is bound as:

  * C# `int[]`
  * `JavaArray<int>`
  * `JavaPrimitiveArray<int>`
  * `JavaInt32Array`

How do we know which to use?  Using assembly-qualified type names
for constructor parameters nicely solved this issue, but if we're not
using them anymore…

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)]`
(present on `JavaInt32Array`), so it didn't know what to do with
the `JavaInt32Array` parameter.

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)

The constructor couldn't be found because
`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.)

Finally, update `CecilCompilerExpressionVisitor` to emit `newobj`
in certain `VisitNew()` invocations.  This was needed while trying:

	partial class JavaCallableExample {
	    [JavaCallable ("getA")]
	    public int[] GetA() => this.a;
	}

in order to fix the IL error:

	% $HOME/.dotnet/tools/ilverify bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll \
	    --tokens --system-module System.Private.CoreLib \
	    -r 'bin/TestDebug-net7.0/*.dll' \
	    -r '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.10/*.dll'
	[IL]: Error [StackUnderflow]: […/bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll : .__<$>_jni_marshal_methods::n_GetA(native int, native int)][offset 0x0000002F] Stack underflow.

Unfortunately, even after the above fix invalid IL was generated during
`jnimarshalmethod-gen` processing, which will be investigated later.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/IL2057
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/il2075
[2]: https://github.com/xamarin/xamarin-android/blob/main/src/Mono.Android/Android.Runtime/AndroidRuntime.cs#L481-L577
  • Loading branch information
jonpryor authored Dec 2, 2023
1 parent 0f1efeb commit 005c914
Show file tree
Hide file tree
Showing 29 changed files with 526 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,10 @@ protected override Expression VisitNew (
base.VisitNew (node);
if (node.Constructor == null && node.Type.IsValueType) {
il.Emit (OpCodes.Initobj, assemblyDef.MainModule.ImportReference (node.Type));
} else {
} else if (node.Type.IsValueType) {
il.Emit (OpCodes.Call, assemblyDef.MainModule.ImportReference (node.Constructor));
} else {
il.Emit (OpCodes.Newobj, assemblyDef.MainModule.ImportReference (node.Constructor));
}
return node;
}
Expand Down
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 Expand Up @@ -825,9 +831,7 @@ void GenerateRegisterType (TextWriter sw, JavaCallableWrapperGenerator self, str
case JavaPeerStyle.JavaInterop1:
sw.Write ("net.dot.jni.ManagedPeer.registerNativeMembers (");
sw.Write (self.name);
sw.Write (".class, \"");
sw.Write (managedTypeName);
sw.Write ("\", ");
sw.Write (".class, ");
sw.Write (field);
sw.WriteLine (");");
break;
Expand Down Expand Up @@ -1025,9 +1029,7 @@ void GenerateConstructor (Signature ctor, TextWriter sw)
switch (CodeGenerationTarget) {
case JavaPeerStyle.JavaInterop1:
sw.Write ("net.dot.jni.ManagedPeer.construct (this, \"");
sw.Write (type.GetPartialAssemblyQualifiedName (cache));
sw.Write ("\", \"");
sw.Write (ctor.ManagedParameters);
sw.Write (ctor.JniSignature);
sw.Write ("\", new java.lang.Object[] { ");
sw.Write (ctor.ActivateCall);
sw.WriteLine (" });");
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
4 changes: 4 additions & 0 deletions src/Java.Interop/Java.Interop/JavaProxyObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Java.Interop {

Expand Down Expand Up @@ -72,6 +73,7 @@ public override bool Equals (object? obj)
}

// TODO: Keep in sync with the code generated by ExportedMemberBuilder
[UnmanagedFunctionPointer (CallingConvention.Winapi)]
delegate bool EqualsMarshalMethod (IntPtr jnienv, IntPtr n_self, IntPtr n_value);
static bool Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value)
{
Expand All @@ -92,6 +94,7 @@ static bool Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value)
}

// TODO: Keep in sync with the code generated by ExportedMemberBuilder
[UnmanagedFunctionPointer (CallingConvention.Winapi)]
delegate int GetHashCodeMarshalMethod (IntPtr jnienv, IntPtr n_self);
static int GetHashCode (IntPtr jnienv, IntPtr n_self)
{
Expand All @@ -109,6 +112,7 @@ static int GetHashCode (IntPtr jnienv, IntPtr n_self)
}
}

[UnmanagedFunctionPointer (CallingConvention.Winapi)]
delegate IntPtr ToStringMarshalMethod (IntPtr jnienv, IntPtr n_self);
static IntPtr ToString (IntPtr jnienv, IntPtr n_self)
{
Expand Down
4 changes: 4 additions & 0 deletions src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ static Dictionary<string, Type> InitJniBuiltinSimpleReferenceToType ()
{
return new Dictionary<string, Type> (StringComparer.Ordinal) {
{"java/lang/String", typeof (string)},
{"net/dot/jni/internal/JavaProxyObject", typeof (JavaProxyObject)},
{"net/dot/jni/internal/JavaProxyThrowable", typeof (JavaProxyThrowable)},
{"net/dot/jni/ManagedPeer", typeof (ManagedPeer)},
{"V", typeof (void)},
{"Z", typeof (Boolean)},
{"java/lang/Boolean", typeof (Boolean?)},
Expand All @@ -156,6 +159,7 @@ static KeyValuePair<Type, JniValueMarshaler>[] InitJniBuiltinMarshalers ()
{
return new []{
new KeyValuePair<Type, JniValueMarshaler>(typeof (string), JniStringValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (JavaProxyObject), ProxyValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (Boolean), JniBooleanValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (Boolean?), JniNullableBooleanValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (SByte), JniSByteValueMarshaler.Instance),
Expand Down
4 changes: 4 additions & 0 deletions src/Java.Interop/Java.Interop/JniBuiltinMarshalers.tt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ namespace Java.Interop {
{
return new Dictionary<string, Type> (StringComparer.Ordinal) {
{"java/lang/String", typeof (string)},
{"net/dot/jni/internal/JavaProxyObject", typeof (JavaProxyObject)},
{"net/dot/jni/internal/JavaProxyThrowable", typeof (JavaProxyThrowable)},
{"net/dot/jni/ManagedPeer", typeof (ManagedPeer)},
{"V", typeof (void)},
<#
foreach (var type in types) {
Expand All @@ -119,6 +122,7 @@ namespace Java.Interop {
{
return new []{
new KeyValuePair<Type, JniValueMarshaler>(typeof (string), JniStringValueMarshaler.Instance),
new KeyValuePair<Type, JniValueMarshaler>(typeof (JavaProxyObject), ProxyValueMarshaler.Instance),
<#
foreach (var type in types) {
#>
Expand Down
16 changes: 16 additions & 0 deletions src/Java.Interop/Java.Interop/JniMemberSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ public JniMemberSignature (string memberName, string memberSignature)
this.memberSignature = memberSignature;
}

internal static IEnumerable<JniTypeSignature> GetParameterTypesFromMethodSignature (string jniMethodSignature)
{
if (jniMethodSignature.Length < "()V".Length || jniMethodSignature [0] != '(' ) {
throw new ArgumentException (
$"Member signature `{jniMethodSignature}` is not a method signature. Method signatures must start with `(`.",
nameof (jniMethodSignature));
}
int index = 1;
while (index < jniMethodSignature.Length &&
jniMethodSignature [index] != ')') {
var (start, length) = ExtractType (jniMethodSignature, ref index);
var jniType = jniMethodSignature.Substring (start, length);
yield return JniTypeSignature.Parse (jniType);
}
}

public static int GetParameterCountFromMethodSignature (string jniMethodSignature)
{
if (jniMethodSignature.Length < "()V".Length || jniMethodSignature [0] != '(' ) {
Expand Down
39 changes: 35 additions & 4 deletions src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,42 @@ internal JniInstanceMethods GetConstructorsForType (Type declaringType)
if (declaringType == DeclaringType)
return this;

JniInstanceMethods? methods;

lock (SubclassConstructors) {
if (!SubclassConstructors.TryGetValue (declaringType, out var methods)) {
methods = new JniInstanceMethods (declaringType);
SubclassConstructors.Add (declaringType, methods);
}
if (SubclassConstructors.TryGetValue (declaringType, out methods))
return methods;
}
// Init outside of `lock` in case we have recursive access:
// System.ArgumentException: An item with the same key has already been added. Key: Java.Interop.JavaProxyThrowable
// at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
// at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
// at Java.Interop.JniPeerMembers.JniInstanceMethods.GetConstructorsForType(Type declaringType) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 80
// at Java.Interop.JniPeerMembers.JniInstanceMethods.GetConstructorsForType(Type declaringType) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 80
// at Java.Interop.JniPeerMembers.JniInstanceMethods.StartCreateInstance(String constructorSignature, Type declaringType, JniArgumentValue* parameters) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 146
// at Java.Interop.JavaException..ctor(String message) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JavaException.cs:line 52
// at Java.Interop.JavaProxyThrowable..ctor(Exception exception) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JavaProxyThrowable.cs:line 15
// at Java.Interop.JniEnvironment.Exceptions.Throw(Exception e) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniEnvironment.Errors.cs:line 39
// at Java.Interop.JniRuntime.RaisePendingException(Exception pendingException) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniRuntime.cs:line 444
// at Java.Interop.JniTransition.Dispose() in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniTransition.cs:line 39
// at Java.Interop.ManagedPeer.RegisterNativeMembers(IntPtr jnienv, IntPtr klass, IntPtr n_nativeClass, IntPtr n_methods) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/ManagedPeer.cs:line 195
// at Java.Interop.NativeMethods.java_interop_jnienv_find_class(IntPtr jnienv, IntPtr& thrown, String classname)
// at Java.Interop.NativeMethods.java_interop_jnienv_find_class(IntPtr jnienv, IntPtr& thrown, String classname)
// at Java.Interop.JniEnvironment.Types.TryRawFindClass(IntPtr env, String classname, IntPtr& klass, IntPtr& thrown) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs:line 135
// at Java.Interop.JniEnvironment.Types.TryFindClass(String classname, Boolean throwOnError) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs:line 49
// at Java.Interop.JniEnvironment.Types.FindClass(String classname) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs:line 37
// at Java.Interop.JniType..ctor(String classname) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniType.cs:line 51
// at Java.Interop.JniPeerMembers.JniInstanceMethods..ctor(Type declaringType) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 27
// at Java.Interop.JniPeerMembers.JniInstanceMethods.GetConstructorsForType(Type declaringType) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 77
// at Java.Interop.JniPeerMembers.JniInstanceMethods.StartCreateInstance(String constructorSignature, Type declaringType, JniArgumentValue* parameters) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 146
// at Java.Lang.Object..ctor() in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.Object.cs:line 32
// at Java.BaseTests.MyIntConsumer..ctor(Action`1 action) in /Users/jon/Developer/src/xamarin/java.interop/tests/Java.Base-Tests/Java.Base/JavaToManagedTests.cs:line 77
// at Java.BaseTests.JavaToManagedTests.InterfaceInvokerMethod() in /Users/jon/Developer/src/xamarin/java.interop/tests/Java.Base-Tests/Java.Base/JavaToManagedTests.cs:line 26
methods = new JniInstanceMethods (declaringType);
lock (SubclassConstructors) {
if (SubclassConstructors.TryGetValue (declaringType, out var m))
return m;
SubclassConstructors.Add (declaringType, methods);
return methods;
}
}
Expand Down
Loading

0 comments on commit 005c914

Please sign in to comment.