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];