Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit aa5e597

Browse files
jpobstjonpryor
authored andcommitted
[JNIEnv] Add support for Kotlin's unsigned types. (#4054)
Fixes: dotnet/java-interop#525 Fixes: dotnet/java-interop#543 Context: dotnet/java-interop@476597b...4565369 Context: dotnet/java-interop@71afce5 Context: https://github.com/Kotlin/KEEP/blob/13b67668ccc5b4741ecc37d0dd050fd77227c035/proposals/unsigned-types.md Context: https://kotlinlang.org/docs/reference/basic-types.html#unsigned-integers Bumps to xamarin/Java.Interop/master@45653697 * dotnet/java-interop@4565369: [generator] Improve generic type lookup (#552) * dotnet/java-interop@71afce5: [generator] Support Kotlin's unsigned types (#539) * dotnet/java-interop@f26bc27: [Java.Interop] use Dictionary<string, *>(StringComparer.Ordinal) (#550) Kotlin has experimental (!) support for unsigned types, providing the following value types: * `kotlin.UByte`: marshals as a Java `byte`/JNI `B`; equivalent to C# `byte` * `kotlin.UInt`: marshals as an Java `int`/JNI `I`; equivalent to C# `uint` * `kotlin.ULong`: marshals as a Java `long`/JNI `J`; equivalent to C# `ulong` * `kotlin.UShort`: marshals as a Java `short`/JNI `S`; equivalent to C# `ushort` Kotlin also provides arrays of these types: * `kotlin.UByteArray`: marshals as a Java `byte[]`/JNI `[B`; equivalent to C# `byte[]`, * `kotlin.UIntArray`: marshals as an Java `int[]`/JNI `[I`; equivalent to C# `uint[]`. * `kotlin.ULongArray`: marshals as a Java `long[]`/JNI `[J`; equivalent to C# `ulong[]`. * `kotlin.UShortArray`: marshals as a Java `short[]`/JNI `[S`; equivalent to C# `ushort[]`. Kotlin methods which contain unsigned types are "mangled", containing characters which cannot be part of a valid Java identifier. As such, they cannot be invoked from Java code. They *can* be invoked via JNI. To bind these, we have two options: 1. We could ignore all members which use unsigned types, or 2. We could present a "Java-esque" view of the methods. (1) is potentially problematic, as it means that abstract classes and interfaces which contain them could become unbindable, making life more complicated than is necessarily ideal. (2) is viable, but ugly. Consider this Kotlin type: // Kotlin package example; public open class ExampleBase { public fun foo(value : UInt) {} } Run `javap` on the resulting output, and we observe: Compiled from "hello.kt" public class example.ExampleBase { public final void foo-WZ4Q5Ns(int); public example.ExampleBase(); } We could bind `example.ExampleBase.foo-WZ4Q5Ns(int)` in C# by replacing invalid characters with `_`, e.g.: // C# Binding partial class ExampleBase : Java.Lang.Object { [Register ("foo-WZ4Q5Ns", …)] public void Foo_WZ4Q5Ns (int value) {…} } ...but that would be *really* ugly. We could instead just take everything before the `-` as the method name for the C# method name -- resulting in `ExampleBase.Foo()` -- which results in a friendlier name from C#, but opens us up to "collisions" with method overloads: // Kotlin public open class ExampleBase2 { public fun foo(value : Int) {} public fun foo(value : UInt) {} } The C# `ExampleBase` type can't bind *both* `ExampleBase2.foo()` methods as `Foo(int)`. The chosen solution is to "more natively" support Kotlin unsigned types, which allows reasonable disambiguation: // C# binding partial class ExampleBase2 : Java.Lang.Object { [Register ("foo", …)] public void Foo (int value) {…} [Register ("foo-WZ4Q5Ns", …)] public void Foo (uint value) {…} } Java.Interop added support for emitting C# binding code which supports Kotlin unsigned types. For `kotlin.UByte`, `kotlin.UInt`, `kotlin.ULong`, and `kotlin.UShort`, no additional runtime changes are needed to support the binding. The `*Array` counterparts *do* require additional runtime changes. Consider this Kotlin type: // Kotlin class UnsignedInstanceMethods { public open fun uintArrayInstanceMethod (value: UIntArray) : UIntArray { return value; } } The binding for this method would be: // C# binding partial class UnsignedInstanceMethods { [Register ("uintArrayInstanceMethod--ajY-9A", "([I)[I", "")] public unsafe uint[] UintArrayInstanceMethod (uint[] value) { const string __id = "uintArrayInstanceMethod--ajY-9A.([I)[I"; IntPtr native_value = JNIEnv.NewArray ((int[])(object)value); try { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (native_value); var __rm = _members.InstanceMethods.InvokeNonvirtualObjectMethod (__id, this, __args); return (uint[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (uint)); } finally { if (value != null) { JNIEnv.DeleteLocalRef (native_value); } } } } The problem is the `JNIEnv.GetArray(…, typeof(uint))` invocation. This eventually hits various dictionaries within `JNIEnv`, which requires additional support so that `uint` can be marshaled properly. Previous versions of Xamarin.Android do not contain support for marshaling arrays of unsigned types. As such, support for using Kotlin bindings which contain unsigned types will require Xamarin.Android 10.2.0 and later. ~~ Installer ~~ Add `protobuf-net.dll` to our installers, as it is required to read the metadata that Kotlin puts within `.class` files. ~~ Unit Tests ~~ On-device tests for calling Kotlin code that uses unsigned types have been added. As we don't currently have a way of acquiring the Kotlin compiler for builds, the test `.jar` and Kotlin's `org.jetbrains.kotlin.kotlin-stdlib.jar` are copied into the repo. They are *not* redistributed. The source of the test `.jar` is provided for future use. ~~ Warnings ~~ Kotlin unsigned types are still experimental. If Kotlin changes how they are represented in Java bytecode, bindings which use them will break. Methods which use unsigned types *cannot* be virtual, nor can they be used in bound interfaces. This is because Java source code is currently used as an "intermediary" for Java <-> Managed transitions, and Java cannot override Kotlin "mangled" methods. If the virtual method is declared on a class, it will be bound as a non-`virtual` method. If the method is on an interface, it will be bound, but the interface will not actually be implementable; a [XA4213 build-time error][0] will be generated. [0]: dotnet/java-interop@3bf5333
1 parent 46eeb3a commit aa5e597

File tree

9 files changed

+213
-1
lines changed

9 files changed

+213
-1
lines changed

build-tools/installers/create-installers.targets

+1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
<_MSBuildFiles Include="$(MSBuildSrcDir)\java_runtime.dex" />
189189
<_MSBuildFiles Include="$(MSBuildSrcDir)\java_runtime_fastdev.dex" />
190190
<_MSBuildFiles Include="$(MSBuildSrcDir)\manifestmerger.jar" />
191+
<_MSBuildFiles Include="$(MSBuildSrcDir)\protobuf-net.dll" />
191192
<_MSBuildFiles Include="$(MSBuildSrcDir)\SgmlReaderDll.dll" />
192193
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.Aapt.targets" />
193194
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.Aapt2.targets" />

external/Java.Interop

src/Mono.Android/Android.Runtime/JNIEnv.cs

+38
Original file line numberDiff line numberDiff line change
@@ -1123,16 +1123,31 @@ static Dictionary<Type, Func<Type, IntPtr, int, Array>> CreateNativeArrayToManag
11231123
CopyArray (source, r);
11241124
return r;
11251125
} },
1126+
{ typeof (ushort), (type, source, len) => {
1127+
var r = new ushort [len];
1128+
CopyArray (source, r);
1129+
return r;
1130+
} },
11261131
{ typeof (int), (type, source, len) => {
11271132
var r = new int[len];
11281133
CopyArray (source, r);
11291134
return r;
11301135
} },
1136+
{ typeof (uint), (type, source, len) => {
1137+
var r = new uint[len];
1138+
CopyArray (source, r);
1139+
return r;
1140+
} },
11311141
{ typeof (long), (type, source, len) => {
11321142
var r = new long[len];
11331143
CopyArray (source, r);
11341144
return r;
11351145
} },
1146+
{ typeof (ulong), (type, source, len) => {
1147+
var r = new ulong[len];
1148+
CopyArray (source, r);
1149+
return r;
1150+
} },
11361151
{ typeof (float), (type, source, len) => {
11371152
var r = new float[len];
11381153
CopyArray (source, r);
@@ -1592,6 +1607,29 @@ public static Java.Lang.Object[] ToObjectArray<T> (T[] array)
15921607
return ret;
15931608
}
15941609

1610+
public static unsafe void CopyArray (IntPtr src, uint[] dest)
1611+
{
1612+
if (src == IntPtr.Zero)
1613+
return;
1614+
fixed (uint* __p = dest)
1615+
JniEnvironment.Arrays.GetIntArrayRegion (new JniObjectReference (src), 0, dest.Length, (int*) __p);
1616+
}
1617+
1618+
public static unsafe void CopyArray (IntPtr src, ushort[] dest)
1619+
{
1620+
if (src == IntPtr.Zero)
1621+
return;
1622+
fixed (ushort* __p = dest)
1623+
JniEnvironment.Arrays.GetShortArrayRegion (new JniObjectReference (src), 0, dest.Length, (short*) __p);
1624+
}
1625+
1626+
public static unsafe void CopyArray (IntPtr src, ulong[] dest)
1627+
{
1628+
if (src == IntPtr.Zero)
1629+
return;
1630+
fixed (ulong* __p = dest)
1631+
JniEnvironment.Arrays.GetLongArrayRegion (new JniObjectReference (src), 0, dest.Length, (long*) __p);
1632+
}
15951633
#if ANDROID_8
15961634
[DllImport ("libjnigraphics.so")]
15971635
static extern int AndroidBitmap_getInfo (IntPtr env, IntPtr jbitmap, out Android.Graphics.AndroidBitmapInfo info);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using Com.Xamarin.Android;
3+
using Foo;
4+
using Java.Lang;
5+
using NUnit.Framework;
6+
7+
namespace Xamarin.Android.JcwGenTests
8+
{
9+
[TestFixture]
10+
public class KotlinUnsignedTypesTests
11+
{
12+
[Test]
13+
public void TestUnsignedTypeMembers ()
14+
{
15+
var foo = new Foo.UnsignedInstanceMethods ();
16+
17+
Assert.AreEqual (uint.MaxValue, foo.UnsignedInstanceMethod (uint.MaxValue));
18+
19+
Assert.AreEqual (3u, foo.UnsignedInstanceProperty);
20+
foo.UnsignedInstanceProperty = uint.MaxValue;
21+
Assert.AreEqual (uint.MaxValue, foo.UnsignedInstanceProperty);
22+
23+
Assert.AreEqual (ushort.MaxValue, foo.UshortInstanceMethod (ushort.MaxValue));
24+
25+
Assert.AreEqual (3u, foo.UshortInstanceProperty);
26+
foo.UshortInstanceProperty = ushort.MaxValue;
27+
Assert.AreEqual (ushort.MaxValue, foo.UshortInstanceProperty);
28+
29+
Assert.AreEqual (ulong.MaxValue, foo.UlongInstanceMethod (ulong.MaxValue));
30+
31+
Assert.AreEqual (3u, foo.UlongInstanceProperty);
32+
foo.UlongInstanceProperty = ulong.MaxValue;
33+
Assert.AreEqual (ulong.MaxValue, foo.UlongInstanceProperty);
34+
35+
Assert.AreEqual (byte.MaxValue, foo.UbyteInstanceMethod (byte.MaxValue));
36+
37+
Assert.AreEqual (3u, foo.UbyteInstanceProperty);
38+
foo.UbyteInstanceProperty = byte.MaxValue;
39+
Assert.AreEqual (byte.MaxValue, foo.UbyteInstanceProperty);
40+
}
41+
42+
[Test]
43+
public void TestUnsignedArrayTypeMembers ()
44+
{
45+
var foo = new Foo.UnsignedInstanceMethods ();
46+
47+
var uint_array = new uint [] { 1u, 2u, uint.MaxValue };
48+
var ushort_array = new ushort [] { 1, 2, ushort.MaxValue };
49+
var ulong_array = new ulong [] { 1u, 2u, ulong.MaxValue };
50+
var ubyte_array = new byte [] { 1, 2, byte.MaxValue };
51+
52+
Assert.AreEqual (uint_array, foo.UintArrayInstanceMethod (uint_array));
53+
Assert.AreEqual (ushort_array, foo.UshortArrayInstanceMethod (ushort_array));
54+
Assert.AreEqual (ulong_array, foo.UlongArrayInstanceMethod (ulong_array));
55+
Assert.AreEqual (ubyte_array, foo.UbyteArrayInstanceMethod (ubyte_array));
56+
}
57+
58+
[Test]
59+
public void TestUnsignedTypeInterfaceImplementedMembers ()
60+
{
61+
var foo = new Foo.UnsignedInterfaceImplementedMethods ();
62+
63+
var uint_array = new uint [] { 1u, 2u, uint.MaxValue };
64+
65+
Assert.AreEqual (uint.MaxValue, foo.UnsignedInterfaceMethod (uint.MaxValue));
66+
67+
Assert.AreEqual (3u, foo.UnsignedInterfaceProperty);
68+
foo.UnsignedInterfaceProperty = uint.MaxValue;
69+
Assert.AreEqual (uint.MaxValue, foo.UnsignedInterfaceProperty);
70+
71+
Assert.AreEqual (uint_array, foo.UnsignedArrayInterfaceMethod (uint_array));
72+
}
73+
74+
[Test]
75+
public void TestUnsignedTypeAbstractImplementedMembers ()
76+
{
77+
var foo = new Foo.UnsignedAbstractImplementedMethods ();
78+
79+
Assert.AreEqual (uint.MaxValue, foo.UnsignedAbstractMethod (uint.MaxValue));
80+
81+
Assert.AreEqual (3u, foo.UnsignedAbstractClassProperty);
82+
foo.UnsignedAbstractClassProperty = uint.MaxValue;
83+
Assert.AreEqual (uint.MaxValue, foo.UnsignedAbstractClassProperty);
84+
}
85+
86+
[Test]
87+
public void TestUnsignedConstant ()
88+
{
89+
Assert.AreEqual (3u, Foo.UnsignedMethodsKt.SubsystemDeprecated);
90+
}
91+
}
92+
}

tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
</ItemGroup>
5353
<ItemGroup>
5454
<Compile Include="BindingTests.cs" />
55+
<Compile Include="KotlinUnsignedTypesTests.cs" />
5556
<Compile Include="DimBindingTests.cs" />
5657
<Compile Include="ExceptionTests.cs" />
5758
<Compile Include="MainActivity.cs" />
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Compile with:
2+
// kotlinc UnsignedMethods.kt -d KotlinUnsignedTypes.jar
3+
4+
package foo;
5+
6+
@kotlin.ExperimentalUnsignedTypes
7+
const val SUBSYSTEM_DEPRECATED: UInt = 3u
8+
9+
@kotlin.ExperimentalUnsignedTypes
10+
open class UnsignedInstanceMethods {
11+
public open fun unsignedInstanceMethod (value: UInt) : UInt { return value; }
12+
13+
public open fun ushortInstanceMethod (value: UShort) : UShort { return value; }
14+
15+
public open fun ulongInstanceMethod (value: ULong) : ULong { println ("kotlin: " + value); return value; }
16+
17+
public open fun ubyteInstanceMethod (value: UByte) : UByte { return value; }
18+
19+
var signedInstanceProperty: Int = 3
20+
21+
var unsignedInstanceProperty: UInt = 3u
22+
23+
var ushortInstanceProperty: UShort = 3u
24+
25+
var ulongInstanceProperty: ULong = 3u
26+
27+
var ubyteInstanceProperty: UByte = 3u
28+
29+
public open fun uintArrayInstanceMethod (value: UIntArray) : UIntArray { return value; }
30+
31+
public open fun ushortArrayInstanceMethod (value: UShortArray) : UShortArray { return value; }
32+
33+
public open fun ulongArrayInstanceMethod (value: ULongArray) : ULongArray { return value; }
34+
35+
public open fun ubyteArrayInstanceMethod (value: UByteArray) : UByteArray { return value; }
36+
37+
public open fun intArrayInstanceMethod (value: IntArray) : IntArray { return value; }
38+
}
39+
40+
@kotlin.ExperimentalUnsignedTypes
41+
open class UnsignedInterfaceImplementedMethods : UnsignedInterface {
42+
public override open fun unsignedInterfaceMethod (value: UInt) : UInt { return value; }
43+
44+
public override open fun unsignedArrayInterfaceMethod (value: UIntArray) : UIntArray { return value; }
45+
46+
override var unsignedInterfaceProperty: UInt = 3u
47+
48+
override var signedInterfaceProperty: Int = 3
49+
}
50+
51+
@kotlin.ExperimentalUnsignedTypes
52+
open class UnsignedAbstractImplementedMethods : UnsignedAbstractClass () {
53+
public override open fun unsignedAbstractMethod (value: UInt) : UInt { return value; }
54+
55+
override var unsignedAbstractClassProperty: UInt = 3u
56+
}
57+
58+
@kotlin.ExperimentalUnsignedTypes
59+
abstract class UnsignedAbstractClass {
60+
abstract fun unsignedAbstractMethod (value: UInt) : UInt
61+
62+
abstract var unsignedAbstractClassProperty: UInt
63+
}
64+
65+
@kotlin.ExperimentalUnsignedTypes
66+
interface UnsignedInterface {
67+
fun unsignedInterfaceMethod (value: UInt) : UInt
68+
69+
fun unsignedArrayInterfaceMethod (value: UIntArray) : UIntArray
70+
71+
var unsignedInterfaceProperty: UInt
72+
73+
var signedInterfaceProperty: Int
74+
}

tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj

+6
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@
134134
<OutputFile>Jars/xamarin-test.jar</OutputFile>
135135
</TestJarEntry>
136136
</ItemGroup>
137+
<ItemGroup>
138+
<EmbeddedJar Include="Jars\KotlinUnsignedTypes.jar" />
139+
</ItemGroup>
140+
<ItemGroup>
141+
<EmbeddedReferenceJar Include="Jars\org.jetbrains.kotlin.kotlin-stdlib.jar" />
142+
</ItemGroup>
137143
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.Bindings.targets" />
138144
<Import Project="..\..\..\build-tools\scripts\Jar.targets" />
139145
<Import Project="Xamarin.Android.McwGen-Tests.targets" />

0 commit comments

Comments
 (0)