Passing arrays through the JNI

I can’t seem to get an array accross the JNI boundary and into the happy land of Java. I’ve tried so many things that at this point unless someone has already pulled it off it’s time for a bug report. I’ve tried sending String arrays, byte arrays, IntPtr arrays, object arrays, etc all with no avail.

Methods attempted:

  • simple JNI route instantiating and AndroidJavaObject and calling a method that takes an array. I have tried using the To***Array helper methods with no luck. While still in Unity I can get the proper array size with the getArrayLength method though.
  • cereating the strongly typed array manually using the JNI NewArray and set object methods. Same thing, the array has a proper size but it still ends up being null in Java land
  • totally manual creation of the array and every object in it. Manual class and method lookups as well. Still get null in Java.

Had anyone successfully passed an array to Java? If not it’s bug report time!

Hey :slight_smile:

I have also had this problem and not found a “nice” solution :frowning: I have ended up using a character that will not be used to delimit a string C# side and then split it Java side. So something like this:

stringValue1[stringValue2[…

Obviously if the caller can pass free text this may not be suitable for you.

Thanks
Dan :slight_smile:

You guys aren’t really understanding the JNI. Here is a segment from the Flurry wrapper I wrote, where I pass a Dictionary in to Java via the JNI.

public void logEvent(string eventId, Dictionary<string, string> parameters)
{
	using(AndroidJavaObject obj_HashMap = new AndroidJavaObject("java.util.HashMap")) 
	{
		// Call 'put' via the JNI instead of using helper classes to avoid:
		// 	"JNI: Init'd AndroidJavaObject with null ptr!"
		IntPtr method_Put = AndroidJNIHelper.GetMethodID(obj_HashMap.GetRawClass(), "put", 
			"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
		
		object[] args = new object[2];
		foreach(KeyValuePair<string, string> kvp in parameters)
		{
			using(AndroidJavaObject k = new AndroidJavaObject("java.lang.String", kvp.Key))
			{
				using(AndroidJavaObject v = new AndroidJavaObject("java.lang.String", kvp.Value))
				{
					args[0] = k;
					args[1] = v;
					AndroidJNI.CallObjectMethod(obj_HashMap.GetRawObject(), 
						method_Put, AndroidJNIHelper.CreateJNIArgArray(args));
				}
			}
		}
		cls_FlurryAgent.CallStatic("logEvent", eventId, obj_HashMap);
	}
}

@Zero, dictionaries, hashmaps, bundles, they are all simple to translate over. Arrays are a totally different beast though. Everything works fine from C using the JNI:

jobjectArray arr = env->NewObjectArray( 5, env->FindClass( "java/lang/String" ), env->NewStringUTF( "" ) );
	const char *message[5]= { "first", "second", "third", "fourth", "fifth" };

	for( int i = 0; i < 5; i++ )
    	env->SetObjectArrayElement( arr, i, env->NewStringUTF( message[i] ) );

void passStringArrayToJava( JNIEnv *env, jobject arr )
{
	jclass cls = env->FindClass( "NativeTester" );
	jmethodID mid = env->GetStaticMethodID( cls, "passStringArray", "([Ljava/lang/String;)V" );
	if( mid )
		env->CallStaticVoidMethod( cls, mid, arr );
}

The AndroidJNI methods you would expect to work like ToObjectArray, SetObjectArrayElement, NewObjectArray, etc do not though. I did find a workaround but it would be nice to get the AndroidJNI class members working properly.

Prime31
Could you please share the workaround?
And also, have you filled the bug report? :wink:

I apologize for bumping this thread up from so long ago, but it is one of the only results when you search for “Passing array from Unity to Java/Android”. Since Prime31 declined to mention the workaround he came up with, I came up with my own solution and decided to share:

Note: This is tested on Unity 4.2 only. Your mileage may vary on other versions!

C# Code:

public class TestArrayThroughJNI
{
	private static AndroidJavaObject _plugin;
	
    static GPlayIABPlugin()
    {
		if( Application.platform != RuntimePlatform.Android )
			return;

        //This is apparently good practice, but seems to have no effect anyways!
        AndroidJNI.AttachCurrentThread();

		// find the plugin instance
        using (var pluginClass = new AndroidJavaClass("com.company.myplugin"))
			_plugin = pluginClass.CallStatic<AndroidJavaObject>( "instance" );
	}
    public static void PassArrayToJava()
    {
        float[] testFltArray = {1.5f, 2.5f, 3.5f, 3.6f};

        IntPtr jAryPtr = AndroidJNIHelper.ConvertToJNIArray(testFltArray);
        jvalue[] blah = new jvalue[1];
        blah[0].l = jAryPtr;

        IntPtr methodId = AndroidJNIHelper.GetMethodID(_plugin.GetRawClass(), "testArrayFunction");
        AndroidJNI.CallVoidMethod(_plugin.GetRawObject(), methodId, blah);
    }
}

Then on the Java side of things:

public class JavaTestArray
{
	//Used by Unity to grab the instance of this Java class.
	private static IABUnityPlugin _instance;

	//Magic happens here.
	public void testArrayFunction(float[] test)
	{
		Log.i("Unity", "Float[]: " + test + " and length: " + test.length);
		
		for(int i = 0; i < test.length; i++)
		{
			Log.i("Unity", "[" + i + "] " + test[i]);
		}
	}
	
	public static JavaTestArray instance()
	{
		if(_instance == null)
			_instance = new JavaTestArray();
		
		return _instance;
	}
}

Output:
Float[ ]: [F@405608d0 and length: 4
[0] 1.5
[1] 2.5
[2] 3.5
[3] 3.6

I’ve only tested it with Float[ ] and String[ ] and they both appear to work, so I think this is about as ‘proper’ as a method as it comes.

3 Likes

After doing my own bit of testing/research with this, I now understand why it doesn’t work and thought I would share my findings. The problem is that AndroidJavaObject.CallStatic() has for its 3rd parameter “params object[ ] args”. What’s happening with arrays, such as int[ ], is they are being passed in and parsed by the compiler as an object[ ]. So if you enable debug (AnrdoidJNIHelper.debug = true), you will see LogCat say no method exists with a signature of int;int;int, if you for instance had an array of 3 ints. It’s an absurdly easy thing to fix in the end (after I spent all day, I love a good challenge!).

Just create an object[ ], then assign the first element to the array you want to send.

// Create object array that will be passed to the CallStatic method
object[] objParams = new object[1];
string[] myStrings = new string[2];
myStrings[0] = "Hello";
myStrings[1] = "World";

// By assigning item 1 in the object[], you are preventing the compiler from parsing them into the function call, so the Unity code sees the string[] and creates the Java signature properly
objParams[0] = myStrings;
// Just for grins, let's also receive a string[] back from java (String[] in java, string[] in C#) and do a manual conversion on it treating it like an Object
AndroidJavaClass myClassInst = new AndroidJavaClass("com.your.company.your_class");
AndroidJavaObject returnedStringArray = myClassInst.CallStatic<AndroidJavaObject>("getStringsAfterPassingThemIn", objParams);

if (returnedStringArray != null)
{
  // Make sure the resulting java object is not null
  if (returnedStringArray.GetRawObject().ToInt32() != 0)
  {
    string[] returnedStrings = AndroidJNIHelper.ConvertFromJNIArray<string[]>(obj.GetRawObject());
  }
}

At first, it may seem like doing the following is the best choice to receive a string array (or any array) back, but if null is returned from the Java side, then you get a seg fault because null is not a string[ ], so use the code above to be safe:

// I don't recommend doing this, unless you know you'll never return null from the Java side
AndroidJavaClass myClassInst = new AndroidJavaClass("com.your.company.your_class");
string[] returnedStrings = myClassInst.CallStatic<string[]>("getStringsAfterPassingThemIn", objParams);

If you really want to go crazy and just do everything by hand:

// Create two strings and assign to an object array
IntPtr clazz = AndroidJNI.FindClass("java.lang.String");
IntPtr methId = AndroidJNI.GetMethodID(clazz, "<init>", "(Ljava/lang/String;)V");
IntPtr strId = AndroidJNI.NewStringUTF("Hello");
IntPtr strObj = AndroidJNI.NewObject(clazz, methId, new jvalue[] { new jvalue() { l = strId } });
IntPtr objArray = AndroidJNI.NewObjectArray(2, clazz, strObj);

// Add second string
strId = AndroidJNI.NewStringUTF("World");
strObj = AndroidJNI.NewObject(clazz, methId, new jvalue[] {new jvalue() { l = strId } });
objArray.SetObjectArrayElement(strObj, 1, strId);

// Call your class method
IntPtr clazz2 = AndroidJNI.FindClass("com.your.company.your_class");
IntPtr methId2 = AndroidJNI.GetStaticMethodID(clazz2, "getStringsAfterPassingThemIn", "([Ljava/lang/String;)[Ljava/lang/String;");
AndroidJNI.CallStaticObjectMethod(clazz2, methId2, new jvalue[] { new jvalue() { l = objArray } } );
1 Like

This is great for string arrays, or for any other fixed type (I suppose, I haven’t tested yet with primitives…), but unfortunately it doesn’t work with object arrays, I keep getting a:

“Exception: JNI: Unknown signature for type ‘System.Object’ (obj = System.Object)”

in the logcat, which is a shame since I’d like to pass a variable number of generic arguments…
Does anyone know if there’s a way of doing what I want the “manual” way, maybe creating a ‘translated’ JNI object array beforehand and passing that to the Call<> method?

That’s probably because the AndroidJNI helper classes don’t know how to convert a C# object into a Java object. Or rather, it’s too generic, so you need to do it all manually. See the last part of my post on doing everything by hand. You should be able to create a Java class that will contain the data you need over in Java, then create an instance of that class using the AndroidJNI.NewObject() method. Then you can call methods in that class (CallObjectMethod and similar), specifically setters, to set internal data to the primitive types. In other words, you would create an instance of the Java class from within C# using the AndroidJNI et al classes. Then you could call “SetName(“Some Value”)” on that object from C#. I believe then you can send an array of those objects back to Java, or probably what I would do to make life sane is have a helper function within the java object that calls into a static class somewhere and adds itself to a list. What I mean is that the Java object you are working with would call a static function “AddMeToList(this)” type thing, rather than you having to do all of that on the C# side.

Sounds reasonable, although quite convoluted…
In my case I ended up using just floats, but as far as a generic solution is concerned, I was thinking probably something like JSON would do the job quite good, but is way way easier, and maybe it could end up being even faster, right? My understanding is that anything JNI-related is horribly slow, so creating a JNI object to contain my C# object for each instance in the array could be prohibitively expensive (this is something I need to do several times per second…) In this light, encoding and decoding everything to/from string could be a good alternative… what do you think?

I actually meant to suggest JSON as an alternative the other day. If you just had some simple Java classes, and if you only use the JNI interface occasionally (as opposed to every frame), I would probably go that route, but sometimes a more complex set of data would be easier to transfer with JSON. Of course with JSON, there is overhead with sockets and string manipulation, but it is probably still less than JNI interface overhead, don’t know for sure.

@supernat Thanks for This! How do I retreive the array in Java though?

I tried to call this method from Unity with the objParams as an argument but with no success:

public static void ReceiveStringArray(String[] sNames) {
      
        names = sNames;
      
    }

@Lord_Ned and @supernat
I am now able to pass float[ ] to Java with your explained methods. However, when I try to pass string[ ] I get the following error:

Why can’t I pass arrays of string?