Skip to content

Commit 62635a3

Browse files
committed
[Java.Interop] Support System.Byte, somewhat
Context: dotnet/android#9747 Context: dotnet/android#9812 In the ongoing epic to get MAUI running atop NativeAOT, we hit our longstanding NativeAOT nemesis: P/Invoke: E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidProgramException: InvalidProgram_Specific, IntPtr Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(System.Type, Byte*) E AndroidRuntime: at Internal.Runtime.TypeLoaderExceptionHelper.CreateInvalidProgramException(ExceptionStringID, String) + 0x4c E AndroidRuntime: at Android.Runtime.JNIEnv.monodroid_typemap_managed_to_java(Type, Byte*) + 0x18 E AndroidRuntime: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type) + 0x104 E AndroidRuntime: at Android.Runtime.JNIEnv.GetJniName(Type) + 0x1c E AndroidRuntime: at Android.Runtime.JNIEnv.FindClass(Type) + 0x38 E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray(IJavaObject[]) + 0x28 E AndroidRuntime: at Android.Runtime.JNIEnv.NewArray[T](T[]) + 0x94 E AndroidRuntime: at Android.Graphics.Drawables.LayerDrawable..ctor(Drawable[] layers) + 0xd4 E AndroidRuntime: at Microsoft.Maui.Platform.MauiRippleDrawableExtensions.UpdateMauiRippleDrawableBackground(View, Paint, IButtonStroke, Func`1, Func`1, Action) + 0x2ac (`JNIEnv.monodroid_typemap_managed_to_java()` is P/Invoke.) The reasonable fix/workaround: update `JNIEnv.FindClass(Type)` to instead use `JniRuntime.JniTypeManager.GetTypeSignature(Type)`. (Also known as "embrace more JniRuntime abstractions!".) Unfortunately, this straightforward idea hits a minor schism between the .NET for Android and builtin java-interop world orders: How should Java `byte` be bound? For starters, what *is* a [Java `byte`][0]? > The values of the integral types are integers in the following ranges: > > * For `byte`, from -128 to 127, inclusive The Java `byte` is *signed*! Because of that, and because java-interop originated as a Second System Syndrome rebuild of Xamarin.Android, *of course* java-interop bound Java `byte` as `System.SByte`. .NET for Android, though, bound Java `byte` as `System.Byte`. This "minor" change meant that lots of unit tests started failing, e.g. [`NetworkInterfacesTest.DotNetInterfacesShouldEqualJavaInterfaces()`][2]: System.ArgumentException : Could not determine Java type corresponding to System.Byte[], System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e. Arg_ParamName_Name, type at Android.Runtime.JNIEnv.FindClass(Type ) at Android.Runtime.JNIEnv.AssertCompatibleArrayTypes(IntPtr , Type ) at Android.Runtime.JNIEnv._GetArray(IntPtr , Type ) at Android.Runtime.JNIEnv.GetArray(IntPtr , JniHandleOwnership , Type ) at Java.Net.NetworkInterface.GetHardwareAddress() at System.NetTests.NetworkInterfacesTest.GetInfos(IEnumeration interfaces) at System.NetTests.NetworkInterfacesTest.DotNetInterfacesShouldEqualJavaInterfaces() at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args) at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags ) Rephrased, `runtime.TypeManager.GetTypeSignature(typeof(byte[]))` returned a "default" `JniTypeSignature` instance. It's time to reduce the size of this schism. Update `JniBuiltinMarshalers.GetBuiltInTypeSignature()` so that `TypeCode.Byte` is treated as a synonym for `TypeCode.SByte`. This is in fact all that's needed in order to add support for `byte[]`! It's *not* all that's necessary to fix all unit tests. Update `JniRuntime.JniTypeManager.GetTypeSignature()` and `.GetTypeSignatures()` so that if the type is an open generic type a `System.NotSupportedException` is thrown instead of a `System.ArgumentException`. This fixes [`Java.InteropTests.JnienvTest.NewOpenGenericTypeThrows()`][3]. Also, `JniBuiltinMarshalers.cs` got some hand-made changes, rendering it out of sync with `JniBuiltinMarshalers.tt`. Regenerate it. [0]: https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.2.1 [1]: https://github.com/dotnet/java-interop/blob/f30e420a1638dc013302e85dcf76642c10c26832/Documentation/Motivation.md [2]: https://github.com/dotnet/android/blob/1b1f1452f6b05707418d6605c06e106e6a2a6381/tests/Mono.Android-Tests/Mono.Android-Tests/System.Net/NetworkInterfaces.cs#L107-L137 [3]: https://github.com/dotnet/android/blob/1b1f1452f6b05707418d6605c06e106e6a2a6381/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs#L107-L116
1 parent f30e420 commit 62635a3

File tree

4 files changed

+19
-8
lines changed

4 files changed

+19
-8
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ static bool GetBuiltInTypeSignature (Type type, ref JniTypeSignature signature)
4949
case TypeCode.Boolean:
5050
signature = GetCachedTypeSignature (ref __BooleanTypeSignature, "Z", arrayRank: 0, keyword: true);
5151
return true;
52+
case TypeCode.Byte:
5253
case TypeCode.SByte:
5354
signature = GetCachedTypeSignature (ref __SByteTypeSignature, "B", arrayRank: 0, keyword: true);
5455
return true;

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

+10-3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ namespace Java.Interop {
5757
return true;
5858
<#
5959
foreach (var type in types) {
60+
if (type.Name == "Byte") {
61+
#>
62+
case TypeCode.Byte:
63+
<#
64+
}
6065
#>
6166
case TypeCode.<#= type.Type #>:
6267
signature = GetCachedTypeSignature (ref __<#= type.Type #>TypeSignature, "<#= type.JniType #>", arrayRank: 0, keyword: true);
@@ -185,7 +190,7 @@ namespace Java.Interop {
185190
public override object? CreateValue (
186191
ref JniObjectReference reference,
187192
JniObjectReferenceOptions options,
188-
[DynamicallyAccessedMembers (ConstructorsAndInterfaces)]
193+
[DynamicallyAccessedMembers (Constructors)]
189194
Type? targetType)
190195
{
191196
if (!reference.IsValid)
@@ -196,7 +201,7 @@ namespace Java.Interop {
196201
public override <#= type.Type #> CreateGenericValue (
197202
ref JniObjectReference reference,
198203
JniObjectReferenceOptions options,
199-
[DynamicallyAccessedMembers (ConstructorsAndInterfaces)]
204+
[DynamicallyAccessedMembers (Constructors)]
200205
Type? targetType)
201206
{
202207
if (!reference.IsValid)
@@ -230,6 +235,7 @@ namespace Java.Interop {
230235
state = new JniValueMarshalerState ();
231236
}
232237

238+
[RequiresDynamicCode (ExpressionRequiresUnreferencedCode)]
233239
[RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)]
234240
public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type? targetType)
235241
{
@@ -242,6 +248,7 @@ namespace Java.Interop {
242248
return sourceValue;
243249
}
244250

251+
[RequiresDynamicCode (ExpressionRequiresUnreferencedCode)]
245252
[RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)]
246253
public override Expression CreateReturnValueFromManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue)
247254
{
@@ -256,7 +263,7 @@ namespace Java.Interop {
256263
public override <#= type.Type #>? CreateGenericValue (
257264
ref JniObjectReference reference,
258265
JniObjectReferenceOptions options,
259-
[DynamicallyAccessedMembers (ConstructorsAndInterfaces)]
266+
[DynamicallyAccessedMembers (Constructors)]
260267
Type? targetType)
261268
{
262269
if (!reference.IsValid)

src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public JniTypeSignature GetTypeSignature (Type type)
146146
if (type == null)
147147
throw new ArgumentNullException (nameof (type));
148148
if (type.ContainsGenericParameters)
149-
throw new ArgumentException ($"'{type}' contains a generic type definition. This is not supported.", nameof (type));
149+
throw new NotSupportedException ($"'{type}' contains a generic type definition. This is not supported.");
150150

151151
type = GetUnderlyingType (type, out int rank);
152152

@@ -184,7 +184,7 @@ public IEnumerable<JniTypeSignature> GetTypeSignatures (Type type)
184184
if (type == null)
185185
yield break;
186186
if (type.ContainsGenericParameters)
187-
throw new ArgumentException ($"'{type}' contains a generic type definition. This is not supported.", nameof (type));
187+
throw new NotSupportedException ($"'{type}' contains a generic type definition. This is not supported.");
188188

189189
type = GetUnderlyingType (type, out int rank);
190190

tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void GetTypeSignature_Type ()
1818
Assert.Throws<ArgumentException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (int[,])));
1919
Assert.Throws<ArgumentException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (int[,][])));
2020
Assert.Throws<ArgumentException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (int[][,])));
21-
Assert.Throws<ArgumentException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (Action<>)));
21+
Assert.Throws<NotSupportedException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (Action<>)));
2222
Assert.AreEqual (null, JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (JniRuntimeTest)).SimpleReference);
2323

2424
AssertGetJniTypeInfoForType (typeof (string), "java/lang/String", false, 0);
@@ -40,6 +40,7 @@ public void GetTypeSignature_Type ()
4040
AssertGetJniTypeInfoForType (typeof (StringComparison[]), "[I", true, 1);
4141
AssertGetJniTypeInfoForType (typeof (StringComparison[][]), "[[I", true, 2);
4242

43+
AssertGetJniTypeInfoForType (typeof (byte[]), "[B", true, 1);
4344
AssertGetJniTypeInfoForType (typeof (int[]), "[I", true, 1);
4445
AssertGetJniTypeInfoForType (typeof (int[][]), "[[I", true, 2);
4546
AssertGetJniTypeInfoForType (typeof (int[][][]), "[[[I", true, 3);
@@ -89,14 +90,16 @@ public void GetTypeSignature_Type ()
8990
static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeyword, int arrayRank)
9091
{
9192
var info = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (type);
93+
Assert.IsTrue (info.IsValid, $"info.IsValid for `{type}`");
9294

9395
// `GetTypeSignature() and `GetTypeSignatures()` should be "in sync"; verify that!
9496
var info2 = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignatures (type).FirstOrDefault ();
97+
Assert.IsTrue (info2.IsValid, $"info2.IsValid for `{type}`");
9598

9699
Assert.AreEqual (jniType, info.Name, $"info.Name for `{type}`");
97-
Assert.AreEqual (jniType, info2.Name, $"info.Name for `{type}`");
100+
Assert.AreEqual (jniType, info2.Name, $"info2.Name for `{type}`");
98101
Assert.AreEqual (arrayRank, info.ArrayRank, $"info.ArrayRank for `{type}`");
99-
Assert.AreEqual (arrayRank, info2.ArrayRank, $"info.ArrayRank for `{type}`");
102+
Assert.AreEqual (arrayRank, info2.ArrayRank, $"info2.ArrayRank for `{type}`");
100103
}
101104

102105
[Test]

0 commit comments

Comments
 (0)