Skip to content

Commit 959e14c

Browse files
committed
[Java.Interop] Zero-GREF marshaling for builtins.
Consider the innocuous JavaObjectArray_object_ContractTest, which effectively does: var array = new JavaObjectArray<object>(3); array [0] = new object (); array [1] = 42; array [2] = "C"; Reasonably straightforward, but there's a hidden GREF cost: because we have a JavaObjectArray<object>, we use JavaVM.GetJniMarshalInfoForType(typeof(object)), which doens't provide any marshaling info, so we use the fallback JavaProxyObject.GetProxy() method to "convert" e.g. 42 into an IJavaObject, and the proxy must use a GREF. Which is fine for array[0], but both array[1] and array[2] have proper marshaling support! Yet we'd still take out a GREF for the int and the string constant! Blech! Improve JniMarshal.CreateLocalRef<T>() and JniMarshal.GetValue<T>() so that they take the runtime type into consideration, ahead of `T`. Within JniMarshal.CreateLocalRef<T>(), this involves using JavaVM.GetJniMarshalInfoForType(value.GetType()) (if value is not null), falling back to using JavaVM.GetJniMarshalInfoForType(typeof(T)) if the value is either null or no marshaler was found for value.GetType(). Similarly, within JniMarshal.GetValue<T>(), before using JavaVM.GetObject() we first lookup the JNI type of the target value, and do the JavaVM.GetTypeForJniTypeRefererence() / JavaVM.GetJniMarshalInfoForType() dance to find an optimized marshaler. By doing this, we can ensure that no additional GREFs are used to store 42 or "C" within the JavaObjectArray<object>.
1 parent 6c38ce6 commit 959e14c

File tree

5 files changed

+89
-20
lines changed

5 files changed

+89
-20
lines changed

src/Java.Interop/Java.Interop/JavaVM.cs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -749,22 +749,6 @@ public virtual Type GetTypeForJniSimplifiedTypeReference (string jniTypeReferenc
749749
return null;
750750
}
751751

752-
static readonly KeyValuePair<Type, JniTypeInfo>[] JniBuiltinTypeNameMappings = new []{
753-
new KeyValuePair<Type, JniTypeInfo>(typeof (string), new JniTypeInfo ("java/lang/String")),
754-
755-
new KeyValuePair<Type, JniTypeInfo>(typeof (void), new JniTypeInfo ("V", true)),
756-
757-
new KeyValuePair<Type, JniTypeInfo>(typeof (sbyte), new JniTypeInfo ("B", true)),
758-
new KeyValuePair<Type, JniTypeInfo>(typeof (short), new JniTypeInfo ("S", true)),
759-
new KeyValuePair<Type, JniTypeInfo>(typeof (int), new JniTypeInfo ("I", true) {
760-
}),
761-
new KeyValuePair<Type, JniTypeInfo>(typeof (long), new JniTypeInfo ("J", true)),
762-
new KeyValuePair<Type, JniTypeInfo>(typeof (float), new JniTypeInfo ("F", true)),
763-
new KeyValuePair<Type, JniTypeInfo>(typeof (double), new JniTypeInfo ("D", true)),
764-
new KeyValuePair<Type, JniTypeInfo>(typeof (char), new JniTypeInfo ("C", true)),
765-
new KeyValuePair<Type, JniTypeInfo>(typeof (bool), new JniTypeInfo ("Z", true)),
766-
};
767-
768752
public virtual JniMarshalInfo GetJniMarshalInfoForType (Type type)
769753
{
770754
if (type == null)

src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ namespace Java.Interop {
66

77
partial class JavaVM {
88

9+
static readonly KeyValuePair<Type, JniTypeInfo>[] JniBuiltinTypeNameMappings = new []{
10+
new KeyValuePair<Type, JniTypeInfo>(typeof (string), new JniTypeInfo ("java/lang/String")),
11+
12+
new KeyValuePair<Type, JniTypeInfo>(typeof (void), new JniTypeInfo ("V", typeIsKeyword: true)),
13+
new KeyValuePair<Type, JniTypeInfo>(typeof (void), new JniTypeInfo ("java/lang/Void")),
14+
15+
new KeyValuePair<Type, JniTypeInfo>(typeof (sbyte), new JniTypeInfo ("B", true)),
16+
new KeyValuePair<Type, JniTypeInfo>(typeof (Boolean), new JniTypeInfo ("Z", typeIsKeyword: true)),
17+
new KeyValuePair<Type, JniTypeInfo>(typeof (Boolean), new JniTypeInfo ("java/lang/Boolean")),
18+
new KeyValuePair<Type, JniTypeInfo>(typeof (Char), new JniTypeInfo ("C", typeIsKeyword: true)),
19+
new KeyValuePair<Type, JniTypeInfo>(typeof (Char), new JniTypeInfo ("java/lang/Character")),
20+
new KeyValuePair<Type, JniTypeInfo>(typeof (Int16), new JniTypeInfo ("S", typeIsKeyword: true)),
21+
new KeyValuePair<Type, JniTypeInfo>(typeof (Int16), new JniTypeInfo ("java/lang/Short")),
22+
new KeyValuePair<Type, JniTypeInfo>(typeof (Int32), new JniTypeInfo ("I", typeIsKeyword: true)),
23+
new KeyValuePair<Type, JniTypeInfo>(typeof (Int32), new JniTypeInfo ("java/lang/Integer")),
24+
new KeyValuePair<Type, JniTypeInfo>(typeof (Int64), new JniTypeInfo ("J", typeIsKeyword: true)),
25+
new KeyValuePair<Type, JniTypeInfo>(typeof (Int64), new JniTypeInfo ("java/lang/Long")),
26+
new KeyValuePair<Type, JniTypeInfo>(typeof (Single), new JniTypeInfo ("F", typeIsKeyword: true)),
27+
new KeyValuePair<Type, JniTypeInfo>(typeof (Single), new JniTypeInfo ("java/lang/Float")),
28+
new KeyValuePair<Type, JniTypeInfo>(typeof (Double), new JniTypeInfo ("D", typeIsKeyword: true)),
29+
new KeyValuePair<Type, JniTypeInfo>(typeof (Double), new JniTypeInfo ("java/lang/Double")),
30+
};
31+
932
static readonly KeyValuePair<Type, JniMarshalInfo>[] JniBuiltinMarshalers = new []{
1033
new KeyValuePair<Type, JniMarshalInfo>(typeof (string), new JniMarshalInfo {
1134
GetValueFromJni = JniEnvironment.Strings.ToString,

src/Java.Interop/Java.Interop/JniBuiltinMarshalers.tt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@ namespace Java.Interop {
2222

2323
partial class JavaVM {
2424

25+
static readonly KeyValuePair<Type, JniTypeInfo>[] JniBuiltinTypeNameMappings = new []{
26+
new KeyValuePair<Type, JniTypeInfo>(typeof (string), new JniTypeInfo ("java/lang/String")),
27+
28+
new KeyValuePair<Type, JniTypeInfo>(typeof (void), new JniTypeInfo ("V", typeIsKeyword: true)),
29+
new KeyValuePair<Type, JniTypeInfo>(typeof (void), new JniTypeInfo ("java/lang/Void")),
30+
31+
new KeyValuePair<Type, JniTypeInfo>(typeof (sbyte), new JniTypeInfo ("B", true)),
32+
<#
33+
foreach (var type in types) {
34+
#>
35+
new KeyValuePair<Type, JniTypeInfo>(typeof (<#= type.Type #>), new JniTypeInfo ("<#= type.JniType #>", typeIsKeyword: true)),
36+
new KeyValuePair<Type, JniTypeInfo>(typeof (<#= type.Type #>), new JniTypeInfo ("java/lang/<#= type.Name #>")),
37+
<#
38+
}
39+
#>
40+
};
41+
2542
static readonly KeyValuePair<Type, JniMarshalInfo>[] JniBuiltinMarshalers = new []{
2643
new KeyValuePair<Type, JniMarshalInfo>(typeof (string), new JniMarshalInfo {
2744
GetValueFromJni = JniEnvironment.Strings.ToString,

src/Java.Interop/Java.Interop/JniMarshal.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,36 @@ internal static T GetValue<T> (JniReferenceSafeHandle handle, JniHandleOwnership
5151
return (T) proxy.Value;
5252
}
5353

54+
if (target is T) {
55+
JniEnvironment.Handles.Dispose (handle, transfer);
56+
return (T) target;
57+
}
58+
5459
var info = jvm.GetJniMarshalInfoForType (typeof (T));
5560
if (info.GetValueFromJni != null) {
5661
return (T) info.GetValueFromJni (handle, transfer, typeof (T));
5762
}
5863

64+
var targetType = jvm.GetTypeForJniTypeRefererence (handle.GetJniTypeName ());
65+
if (targetType != null &&
66+
typeof (T).IsAssignableFrom (targetType) &&
67+
(info = jvm.GetJniMarshalInfoForType (targetType)).GetValueFromJni != null) {
68+
return (T) info.GetValueFromJni (handle, transfer, targetType);
69+
}
70+
5971
return (T) jvm.GetObject (handle, transfer, typeof (T));
6072
}
6173

6274
internal static JniLocalReference CreateLocalRef<T> (T value)
6375
{
6476
var jvm = JniEnvironment.Current.JavaVM;
65-
var info = jvm.GetJniMarshalInfoForType (typeof (T));
77+
78+
var info = new JniMarshalInfo ();
79+
if (value != null)
80+
info = jvm.GetJniMarshalInfoForType (value.GetType ());
81+
if (info.CreateLocalRef == null)
82+
info = jvm.GetJniMarshalInfoForType (typeof (T));
83+
6684
if (info.CreateLocalRef != null) {
6785
return info.CreateLocalRef (value);
6886
}

src/Java.Interop/Tests/Java.Interop/JavaObjectArrayTest.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,36 @@ public void ObjectArrayType ()
129129

130130
[TestFixture]
131131
public class JavaObjectArray_object_ContractTest : JavaObjectArrayContractTest<object> {
132-
protected override object CreateValueA () {return new object ();}
133-
protected override object CreateValueB () {return new object ();}
134-
protected override object CreateValueC () {return new object ();}
132+
static readonly object a = new object ();
133+
134+
protected override object CreateValueA () {return a;}
135+
protected override object CreateValueB () {return 42;}
136+
protected override object CreateValueC () {return "C";}
137+
138+
int grefStartCount;
139+
140+
[TestFixtureSetUp]
141+
public void BeginCheckGlobalRefCount ()
142+
{
143+
// For diagnostic tracking purposes
144+
using (var o = new JavaObject ())
145+
o.RegisterWithVM ();
146+
// So that the JavaProxyObject.TypeRef GREF isn't counted.
147+
using (var o = new JavaObjectArray<object> (1))
148+
o [0] = a;
149+
grefStartCount = JniEnvironment.Current.JavaVM.GlobalReferenceCount;
150+
}
151+
152+
[TestFixtureTearDown]
153+
public void EndCheckGlobalRefCount ()
154+
{
155+
using (var o = new JavaObject ())
156+
o.RegisterWithVM ();
157+
int gref = JniEnvironment.Current.JavaVM.GlobalReferenceCount;
158+
Assert.IsTrue (gref <= (grefStartCount),
159+
string.Format ("JNI global references: grefStartCount={0}; gref={1}", grefStartCount, gref));
160+
GC.Collect ();
161+
}
135162

136163
[Test]
137164
public void ObjectArrayType ()

0 commit comments

Comments
 (0)