josephmoresena / NativeAOT-AndroidHelloJniLib

This repo is a sample of an Android JNI library compilated using NativeAOT
MIT License
60 stars 6 forks source link

Cast JObject to JByteArray #6

Closed malluce closed 3 months ago

malluce commented 6 months ago

Hi, I'm using your library to call a Java method with signature "([B)[B". That is, the return type is a byte array.

For this, I use CallObjectMethodADelegate and pass a pointer to my input array.

JObjectLocalRef result = callObjectMethod(jEnv, jObj, jMethodId, arrInput.GetUnsafeIntPtr());

I'm now stuck at processing the return type. I want to transform the result somehow to a C# byte array. Other JNI libraries directly support jbytearray, which would allow casting the jobject to jbytearray and then go from there. However, this seems not possible here.

I'm looking forward to your answers!

Greetings, Felix

malluce commented 6 months ago

I tried the following approach, but the csharpresult does not contain what i expect the method to return.

IntPtr resultPtr = result.GetUnsafeIntPtr();
JArrayLocalRef arrOutput = arrDel(jEnv, 256); // arrDel is NewByteArrayDelegate
setRegionDel(jEnv, arrOutput, 0, 256, resultPtr); // setRegionDel is SetByteArrayRegionDelegate

IntPtr resultArrayPtr = getBytesDel(jEnv, arrOutput, new JBooleanRef(false)); // getBytesDel is GetByteArrayElementsDelegate 
byte[] csharpresult = new byte[256];
Marshal.Copy(resultArrayPtr, csharpresult , 0, 256);
malluce commented 6 months ago

My fix was to change the delegate

internal delegate IntPtr GetByteArrayElementsDelegate(JEnvRef env, JObjectLocalRef jArray, JBooleanRef isCopy);
//internal delegate IntPtr GetByteArrayElementsDelegate(JEnvRef env, JArrayLocalRef jArray, JBooleanRef isCopy);

This way the result can be simply passed in the getByte delegate and the array can be accessed (e.g., via Marshal.Copy). The problem i see here with the library is that JArrayLocalRef and JObjectLocalRef are not type-compatible even though jarray is-a jobject according to the JNI spec (https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html)

josephmoresena commented 6 months ago

To pass a [B to a java method, one must create a JByteArray with JNI, fill it with the desired data, place its JNI reference in a jvalue array, and then pass it to the java method using JNI.

To retrieve the result, again, the method return is a JNI reference, so you need to get elements or region and then once you have a pointer to the data, copy it from Java memory to .NET memory.

JNI reference are no pointers!

Be careful, .GetUnsafeIntPtr() is dangerous; in fact, if you're using an array, you could use GetFixedContext(). The purpose of GetUnsafeIntPtr() is to use memory that is already pinned as stack references.

I'm working in a real JNI .NET solution, in the building branch now Arrays are View objects, so Casting is easier than the main branch.