android jni return multiple variables

I can come up with three different ways to do it.

Callback

Call a Java method from your JNI code that takes multiple parameters, set a variable somewhere in your Java code, that you can retrieve when returning from the method.

JNIEXPORT void JNICALL Java_my_package_name_JNIReturnExample_returnWithJavaCallback(JNIEnv *env, jobject javaThis, jfloat param1, jfloat param2)
{
    // Get the class of the current calling object
    jclass clazz = (*env)->GetObjectClass(env, javaThis);

    // Get the method id of the instance method: void javaCallback(float, float) in my.package.name.JNIReturnExample
    jmethodID callback = (*env)->GetMethodID(env, clazz, "javaCallback", "(FF)V");

    // Calls my.package.name.JNIReturnExample#javaCallback(float, float);
    (*env)->CallVoidMethod(env, javaThis, callback, param1, param2);
}

Return a new Java object

Instantiate a Java Object (my.package.name.JNIReturnExample) in JNI, and return it to Java.

JNIEXPORT jobject JNICALL Java_my_package_name_JNIReturnExample_returnObjectValue(JNIEnv *env, jobject javaThis, jfloat param1, jfloat param2)
{
    // Get the class we wish to return an instance of
    jclass clazz = (*env)->FindClass(env, "my/package/name/JNIReturnObject");

    // Get the method id of an empty constructor in clazz
    jmethodID constructor = (*env)->GetMethodID(env, clazz, "<init>", "()V");

    // Create an instance of clazz
    jobject obj = (*env)->NewObject(env, clazz, constructor);

    // Get Field references
    jfieldID param1Field = (*env)->GetFieldID(env, clazz, "param1", "F");
    jfieldID param2Field = (*env)->GetFieldID(env, clazz, "param2", "F");

    // Set fields for object
    (*env)->SetFloatField(env, obj, param1Field, param1);
    (*env)->SetFloatField(env, obj, param2Field, param2);

    // return object
    return obj;
}

Pass a Java object as a parameter, and set its fields

Create a new instance of a Java Object in your Java code, and pass that object as a parameter to your JNI function.

JNIEXPORT void JNICALL Java_my_package_name_JNIReturnExample_setObjectFields(JNIEnv *env, jobject javaThis, jobject obj, jfloat param1, jfloat param2)
{
    // Get the class of the input object
    jclass clazz = (*env)->GetObjectClass(env, obj);

    // Get Field references
    jfieldID param1Field = (*env)->GetFieldID(env, clazz, "param1", "F");
    jfieldID param2Field = (*env)->GetFieldID(env, clazz, "param2", "F");

    // Set fields for object
    (*env)->SetFloatField(env, obj, param1Field, param1);
    (*env)->SetFloatField(env, obj, param2Field, param2);
}

Please note that whichever method you decide to use, you should cache the various JNI types jclass, jmethodID, jfieldID, because JNI lookup operations are slow, and only really need to be performed once.


Caching

To cache the JNI references in the Callback method, and call them using the method:

static jclass myCallbackClass;
static jmethodID myCallbackMethod;

/**
 * Call this method in JNI_OnLoad
 */ 
void CacheCallback()
{
    // Get a reference to the Callback class
    jclass clazz = (*env)->FindClass(env, "my/package/name/JNIReturnExample");

    // Store a global reference, since the local one will be freed when returning from the function.
    myCallbackClass = (*env)->NewGlobalRef(env, clazz);

    // Get a reference to the static callback method
    jmethodID callback = (*env)->GetStaticMethodID(env, myCallbackClass, "jniCallback", "(II)V");

    // jmethodID doesn't need a NewGlobalRef call
    myCallbackMethod = callback;
}

/**
 * Call this method in JNI_OnUnload
 */
void ReleaseCallback()
{
    (*env)->DeleteGlobalRef(env, myCallbackClass);
    myCallbackClass = NULL;

    // jmethodIDs are safe to keep without an explicit global reference, for this reason, we don't need to delete the reference either.
    myCallbackMethod = NULL;
}

There is one more way - jobjectArray

Personally, I needed to return to Java a pair of string and int.

String can easily be put into such array using env->NewStringUTF method but for int a wrapper must be constructed.

native part:

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_example_myapp_CallingClass_writeCfgFile(
    JNIEnv *env,
    jobject obj,
    jobjectArray stringArray,
    jstring filepath){
...     
    std::pair<int,string> ret = generate_config(filePath, reqMsgTypes);

    jobjectArray retobjarr = (jobjectArray)env->NewObjectArray(2, env->FindClass("java/lang/Object"), NULL);
    env->SetObjectArrayElement(retobjarr, 0, NewInteger(env, ret.first));
    env->SetObjectArrayElement(retobjarr, 1, env->NewStringUTF(ret.second.c_str()));

    return retobjarr;
}

jobject NewInteger(JNIEnv* env, int value){
    jclass integerClass = env->FindClass("java/lang/Integer");
    jmethodID integerConstructor = env->GetMethodID(integerClass, "<init>", "(I)V");
    return env->NewObject(integerClass, integerConstructor, static_cast<jint>(value));
}

and on Java side:

Object[] resp = writeCfgFile( msgTypes,  filePath");
Integer i = (Integer)resp[0];
String str = (String)resp[1];