Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mono.android.runtime.JavaObject cannot be cast to byte[] when using List<byte[]> #4098

Closed
BobbyShoe opened this issue Jan 8, 2020 · 5 comments · Fixed by #4788
Closed

mono.android.runtime.JavaObject cannot be cast to byte[] when using List<byte[]> #4098

BobbyShoe opened this issue Jan 8, 2020 · 5 comments · Fixed by #4788
Assignees
Labels
Area: Bindings Issues in Java Library Binding projects.

Comments

@BobbyShoe
Copy link

BobbyShoe commented Jan 8, 2020

Steps to Reproduce

  1. Declare a List<byte[]> in C#. Pass this to a Java library and the mono.android.runtime.JavaObject cannot be cast to byte[] error is received when the object is accessed in Java.

Here is the sample call from the Java library. The call to csdBuffer.get throws the exception.

public static void setCsdBuffers(MediaFormat format, List<byte[]> csdBuffers) {
    for (int i = 0; i < csdBuffers.size(); i++) {
      format.setByteBuffer("csd-" + i, ByteBuffer.wrap(csdBuffers.get(i)));
    }
  }

Seems like a similar issue to the java.lang.ClassCastException information mentioned on this page
https://docs.microsoft.com/en-us/xamarin/android/troubleshooting/troubleshooting#javalangclasscastexception-monoandroidruntimejavaobject-cannot-be-cast-to

However the work around suggested there states to use the similar Java collection for the inner collection in the generic. However in this case a byte[] is the inner collection, so is there some comparable Java type that could be used?

Expected Behavior

A List<byte[]> can be passed between C# and Java code.

Actual Behavior

ClassCastException is thrown when the Java code attempts to access the byte array.

Version Information

Xamarin 16.4.000.307

Running on Android 7.1

Log File

Caused by: java.lang.ClassCastException: mono.android.runtime.JavaObject cannot be cast to byte[]

@jonpryor
Copy link
Member

The bug is that JavaConvert.LocalJniHandleConverters doesn't special-case array types: https://github.com/xamarin/xamarin-android/blob/38a10a17eaf16b052e3c5ffbee502565acc688f5/src/Mono.Android/Java.Interop/JavaConvert.cs#L310-L356

Consider this Java method:

public class Gxa4098 {
	public static int genericListMarshaling (List<byte[]> bytes) {
		int count = 0;
		for (byte[] value : bytes ) {
			count += value.length;
		}
		return count;
	}
}

Which results in the binding:

[Register ("genericListMarshaling", "(Ljava/util/List;)I", "")]
public static unsafe int GenericListMarshaling (global::System.Collections.Generic.IList<byte[]> p0)
{
	const string __id = "genericListMarshaling.(Ljava/util/List;)I";
	IntPtr native_p0 = global::Android.Runtime.JavaList<byte[]>.ToLocalJniHandle (p0);
	try {
		JniArgumentValue* __args = stackalloc JniArgumentValue [1];
		__args [0] = new JniArgumentValue (native_p0);
		var __rm = _members.StaticMethods.InvokeInt32Method (__id, __args);
		return __rm;
	} finally {
		JNIEnv.DeleteLocalRef (native_p0);
	}
}

Which calls Android.Runtime.JavaList<byte[]>.ToLocalJniHandle(): https://github.com/xamarin/xamarin-android/blob/38a10a17eaf16b052e3c5ffbee502565acc688f5/src/Mono.Android/Android.Runtime/JavaList.cs#L949-L961

Which hits the JavaList<T> constructor, which calls JavaList<T>.Add(): https://github.com/xamarin/xamarin-android/blob/38a10a17eaf16b052e3c5ffbee502565acc688f5/src/Mono.Android/Android.Runtime/JavaList.cs#L712

Which calls JavaConvert.WithLocalJniHandle(): https://github.com/xamarin/xamarin-android/blob/38a10a17eaf16b052e3c5ffbee502565acc688f5/src/Mono.Android/Android.Runtime/JavaList.cs#L800-L812

Because JavaConvert.LocalJniHandleConverters doesn't special-case arrays, we hit the "fallback" path, which wraps the array into a JavaObject: https://github.com/xamarin/xamarin-android/blob/38a10a17eaf16b052e3c5ffbee502565acc688f5/src/Mono.Android/Java.Interop/JavaConvert.cs#L353-L356

A fix to consider would be to add array types to JavaConvert.LocalJniHandleConverters, but we might want to instead consider using following the "recursive type" approach used around JNIEnv.NewArray(Array, Type).

@jonpryor
Copy link
Member

Patch for possible unit test:

diff --git a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/BindingTests.cs b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/BindingTests.cs
index d6cb60db..c53be34a 100644
--- a/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/BindingTests.cs
+++ b/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/BindingTests.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 
 using NUnit.Framework;
 
@@ -201,6 +202,19 @@ namespace Xamarin.Android.JcwGenTests {
 			var m = e.Message;
 			Assert.IsTrue (e.GetMessageInvoked);
 		}
+
+		// Context: https://github.com/xamarin/xamarin-android/issues/4098
+		[Test]
+		public void GenericListMarshaling ()
+		{
+			var list = new List<byte[]> {
+				new byte[]{ 1, 2, 3 },
+				new byte[]{ 4, 5 },
+				new byte[]{ 6 },
+			};
+			int count = Com.Xamarin.Android.Gxa4098.GenericListMarshaling (list);
+			Assert.AreEqual (6, count);
+		}
 	}
 
 	class ConstructorTest : Com.Xamarin.Android.CallMethodFromCtor {
diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj
index 044fe4bd..4826e56b 100644
--- a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj
+++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Xamarin.Android.McwGen-Tests.csproj
@@ -97,6 +97,9 @@
     <TestJarEntry Include="java\com\xamarin\android\DataListener.java">
       <OutputFile>Jars/xamarin-test.jar</OutputFile>
     </TestJarEntry>
+    <TestJarEntry Include="java\com\xamarin\android\Gxa4098.java">
+      <OutputFile>Jars/xamarin-test.jar</OutputFile>
+    </TestJarEntry>
     <TestJarEntry Include="java\com\xamarin\android\Logger.java">
       <OutputFile>Jars/xamarin-test.jar</OutputFile>
     </TestJarEntry>
diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/Gxa4098.java b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/Gxa4098.java
new file mode 100644
index 00000000..79ee5f06
--- /dev/null
+++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/java/com/xamarin/android/Gxa4098.java
@@ -0,0 +1,13 @@
+package com.xamarin.android;
+
+import java.util.List;
+
+public class Gxa4098 {
+	public static int genericListMarshaling (List<byte[]> bytes) {
+		int count = 0;
+		for (byte[] value : bytes ) {
+			count += value.length;
+		}
+		return count;
+	}
+}

@jonpryor jonpryor modified the milestones: Under Consideration, d16-6 Jan 10, 2020
@jonpryor jonpryor added the Area: Bindings Issues in Java Library Binding projects. label Jan 10, 2020
@jpobst jpobst modified the milestones: d16-6, Under Consideration Apr 1, 2020
@BenBtg
Copy link

BenBtg commented Jun 8, 2020

I have a Android binding for the Pointr SDK which is impacted by this issue and causes the following exception to be thrown.
java.lang.ClassCastException: mono.android.runtime.JavaObject cannot be cast to float[] at com.qozix.tileview.paths.BasicPathView.onDraw(BasicPathView.java:131) at android.view.View.draw(View.java:21975)

jonpryor pushed a commit that referenced this issue Jun 10, 2020
)

Fixes: #4098

Consider the following Java method:

	// Java
	public static int count (List<int[]> values) {
	    int c = 0;
	    for (int[] v : values) {
	        c += v.length;
	    }
	    return c;
	}

This is bound as:

	// C#
	[Register ("count", "(Ljava/util/List;)I", "")]
	public static unsafe int Count (IList<int[]> p0)
	{
	    const string __id = "count.(Ljava/util/List;)I";
	    IntPtr native_p0 = global::Android.Runtime.JavaList<int[]>.ToLocalJniHandle (p0);
	    try {
	        JniArgumentValue* __args = stackalloc JniArgumentValue [1];
	        __args [0] = new JniArgumentValue (native_p0);
	        var __rm = _members.StaticMethods.InvokeInt32Method (__id, __args);
	        return __rm;
	    } finally {
	        JNIEnv.DeleteLocalRef (native_p0);
	    }
	}

The problem is that when the `InvokeInt32Method()` statement is hit,
*Java* throws an exception:

	mono.android.runtime.JavaObject cannot be cast to int[]

The cause of the problem is that while `JavaList<T>.ToLocalJniHandle()`
created a `java.util.ArrayList` instance, the *contents* of the created
`ArrayList` were `JavaObject` instances, *not* `int[]` instances.
Thus, when the Java code attempts to cast an element from the list to
`int[]`, the cast fails.

The underlying problem is that e.g.
`JavaConvert.WithLocalJniHandle<TValue, TReturn>(…)` didn't handle
array types, so if `TValue` were an array, the array would be wrapped
into a `JavaObject` instance, instead of being "deep marshaled" to a
Java-side array.

Add support for array marshaling to `JavaConvert`, so that
`JavaLists<int[]>.ToLocalJniHandle()` properly creates a Java-side
`ArrayList` which contains `int[]` values.  This fixes the cast.

Note that I looked at also adding cases for Kotlin's unsigned primitive
arrays, however Kotlin only allows their use in very limited
circumstances, and `List<uint[]>` is not allowed:

	// Kotlin
	UnsignedMethods.kt:39:61: error: expecting a '>'
	    public open fun uintListInstanceMethod (value: List<UInt[]>) : List<UInt[]> { return value; }
	                                                            ^
@brendanzagaeski
Copy link
Contributor

Release status update

A new Preview version of Xamarin.Android has now been published that includes the fix for this item. The fix is not yet included in a Release version. I will update this item again when a Release version is available that includes the fix.

Fix included in Xamarin.Android SDK version 11.0.99.9.

Fix included on Windows in Visual Studio 2019 version 16.8 Preview 1. To try the Preview version that includes the fix, check for the latest updates in Visual Studio Preview.

Fix included on macOS in Visual Studio 2019 for Mac version 8.8 Preview 1. To try the Preview version that includes the fix, check for the latest updates on the Preview updater channel

@brendanzagaeski
Copy link
Contributor

Release status update

A new Release version of Xamarin.Android has now been published that includes the fix for this item.

Fix included in Xamarin.Android SDK version 11.1.0.17.

Fix included on Windows in Visual Studio 2019 version 16.8. To get the new version that includes the fix, check for the latest updates or install the most recent release from https://visualstudio.microsoft.com/downloads/.

Fix included on macOS in Visual Studio 2019 for Mac version 8.8. To get the new version that includes the fix, check for the latest updates on the Stable updater channel.

@ghost ghost locked as resolved and limited conversation to collaborators Jun 5, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Area: Bindings Issues in Java Library Binding projects.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants