Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
Comment on lines +390 to +392
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the C# attribute available where we could use nameof()? The resulting IL would be the same, but might help typos.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this assembly doesn't have a reference to Java.Interop.dll.

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;
Comment on lines +44 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this happen on device? This looks like it is O(N^2). Is there a way to rework the data to be a Dictionary<Type, JniPrimitiveArrayInfo> to make this lookup faster? I guess you'd also want the keys to map to duplicate entries as the values.

Copy link
Contributor Author

@jonpryor jonpryor Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, and we're talking O(N**2) on 8 entries, each containing 4 elements within e.ArrayTypes, or 32 entries total. O(N**2) on 32 entries should be negligible.

Is there a way to rework the data to be a Dictionary<Type, JniPrimitiveArrayInfo> to make this lookup faster?

Yes, but as this is static data, it'll be constructed during app startup, even for .NET Android (unless we make it conditional, and please no?), so I'm trying to figure out how to minimize startup overhead.

My assumption (un-timed) is that creating an array of structs will be faster than populating a Dictionary, particularly when we're dealing with so few elements (8).

Copy link
Contributor Author

@jonpryor jonpryor Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, about that init vs. lookup time…

Repro App ```csharp

using System.Diagnostics;

const int CreationCount = 1000000;
const int LookupCount = 1000000;

var a = Stopwatch.StartNew ();
for (int i = 0; i < CreationCount; ++i) {
CreateArray ();
}
a.Stop ();

var d = Stopwatch.StartNew ();
for (int i = 0; i < CreationCount; ++i) {
CreateDict ();
}
d.Stop ();

Console.WriteLine ($"Array Creation: {a.ElapsedMilliseconds}ms");
Console.WriteLine ($" Dict Creation: {d.ElapsedMilliseconds}ms");

var av = CreateArray ();
a = Stopwatch.StartNew ();
for (int i = 0; i < LookupCount; ++i) {
TryArrayLookup (av, typeof (int));
}
a.Stop ();

var bv = CreateDict ();
for (int i = 0; i < LookupCount; ++i) {
TryDictLookup (bv, typeof (int));
}
d.Stop ();
Console.WriteLine ($"Array Lookup: {a.ElapsedMilliseconds}ms");
Console.WriteLine ($" Dict Lookup: {d.ElapsedMilliseconds}ms");

static JniPrimitiveArrayInfo[] CreateArray ()
{
return 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 TryArrayLookup (JniPrimitiveArrayInfo[] array, Type type)
{
foreach (var e in array) {
if (Array.IndexOf (e.ArrayTypes, type) < 0)
continue;
return true;
}
return false;
}

static Dictionary<Type, JniPrimitiveArrayInfo> CreateDict ()
{
return new Dictionary<Type, JniPrimitiveArrayInfo> () {
[typeof (Boolean)] = new ("Z", typeof (Boolean), typeof (Boolean[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaBooleanArray)),
[typeof (SByte) ] = new ("B", typeof (SByte), typeof (SByte[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaSByteArray)),
[typeof (Char) ] = new ("C", typeof (Char), typeof (Char[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaCharArray)),
[typeof (Int16) ] = new ("S", typeof (Int16), typeof (Int16[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaInt16Array)),
[typeof (Int32) ] = new ("I", typeof (Int32), typeof (Int32[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaInt32Array)),
[typeof (Int64) ] = new ("J", typeof (Int64), typeof (Int64[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaInt64Array)),
[typeof (Single) ] = new ("F", typeof (Single), typeof (Single[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaSingleArray)),
[typeof (Double) ] = new ("D", typeof (Double), typeof (Double[]), typeof (JavaArray), typeof (JavaPrimitiveArray), typeof (JavaDoubleArray)),
};
}

static bool TryDictLookup (Dictionary<Type, JniPrimitiveArrayInfo> dict, Type type)
{
foreach (var v in dict.Values) {
if (Array.IndexOf (v.ArrayTypes, type) < 0)
return true;
}
return false;
}

class JavaArray {}
class JavaPrimitiveArray : JavaArray {}
class JavaBooleanArray : JavaPrimitiveArray {}
class JavaSByteArray : JavaPrimitiveArray {}
class JavaCharArray : JavaPrimitiveArray {}
class JavaInt16Array : JavaPrimitiveArray {}
class JavaInt32Array : JavaPrimitiveArray {}
class JavaInt64Array : JavaPrimitiveArray {}
class JavaSingleArray : JavaPrimitiveArray {}
class JavaDoubleArray : JavaPrimitiveArray {}

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;
}
}

struct JniTypeSignature {
public string SimpleName;
public int ArrayRank;
public bool Keyword;
public JniTypeSignature (string simpleName, int arrayRank, bool keyword)
{
SimpleName = simpleName;
ArrayRank = arrayRank;
Keyword = keyword;
}
}


</details>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently <details/> doesn't work like i thought, so here's the execution + summary:

Results:

% dotnet run
Array Creation: 646ms
 Dict Creation: 1131ms
Array Lookup: 29ms
 Dict Lookup: 1131ms

% dotnet run -c Release
Array Creation: 389ms
 Dict Creation: 770ms
Array Lookup: 21ms
 Dict Lookup: 770ms

As expected, array lookup is faster -- half the time! -- and lookup is also much faster with arrays.

That said, this investigation did present a bug in GetBuiltInTypeArraySignature(): it shouldn't have been looking at .ArrayTypes, but instead .PrimitiveType, because the callsite in GetTypeSignature(Type) "unwraps" arrays, which kinda makes that particular codepath unusable, as in GetBuiltInTypeArraySignature() should just be removed entirely. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…and GetBuiltInTypeArraySignature() is required; trying to remove it breaks unit tests. Which means my "repro app" isn't accurate on the lookup side. After updating it, arrays are still faster:

% dotnet run -c Release
Array Creation: 392ms
 Dict Creation: 753ms
Array Lookup: 156ms
 Dict Lookup: 753ms

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing the manual work involved to test, should we have a BenchmarkDotNet project in this repo?

It seems like it would be useful to commit your benchmark above, and we'd have the option to run it later. BDN also does appropriate warmup, runs out of process, does some math, that might generally make it better than a manual benchmark.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should probably have one, and it should probably be in tests/Java.Interop-PerformanceTests (and/or I could "just" add the above app to Java.Interop-PerformanceTests…).

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