This is an old revision of the document!
nativeapplication.zip lab5_tasks.zip
TODO - needs refactoring
Create a new project.
Edit native.c and add the following function:
jstring Java_com_example_student_nativeapplication_MainActivity_getString(JNIEnv *env, jobject thiz) { return (*env)->NewStringUTF(env, "Hello world from JNI!"); }
The equivalent C++ code is:
extern "C" { jstring Java_com_example_student_nativeapplication_MainActivity_getString(JNIEnv *env, jobject thiz); } jstring Java_com_example_student_nativeapplication_MainActivity_getString(JNIEnv *env, jobject thiz) { return env->NewStringUTF("Hello world from JNI!"); }
C++ adds decorators to functions so the ART VM would not be able to locate them if not declared as extern “C”.
As you can see the function name is a bit special: it starts with Java and contains the full class name (including package) and ends with the name of the function. The first two parameters are always a pointer to a JNIEnv class (or structure in C) which is an interface to the virtual machine and a jobject which is a reference to the object this method is called from (or a jclass for static methods).
To call the function from Java you must do two things: load the library and declare the method as a function. Add the following lines to your MainActivity.
static { System.loadLibrary("native"); } private native String getString();
Then, make sure your activity has a TextView (with an id to reference it), and at onCreate, set the TextView to the String returned by getString().
There is a more practical way of generating function prototypes: using javah or by automatically adding the functions using Android Studio. Declare all your native functions in the Java files first and then call javah to generate the headers.
For example:
javah -classpath <sdk>/platforms/android-23/android.jar:android_workspace/native/bin/classes/ com.example.student.nativeapplication.MainActivity
will generate com_example_student_nativeapplication_MainActivity.h which contains
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_native1_MainActivity */ #ifndef _Included_com_example_student_nativeapplication_MainActivity #define _Included_com_example_student_nativeapplication_MainActivity #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_student_nativeapplication_MainActivity * Method: getString * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_student_nativeapplication_MainActivity_getString (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
To use logging from native code you will have to include <android/log.h>. Add this include to native.c. You will have to also link this module against liblog. This already done in the imported project (ldLibs += “log” in gradle.build).
Then, to print something, you need to use this macro:
__android_log_print(log level, string tag, printf format, variables according to format);
For example
__android_log_print(ANDROID_LOG_VERBOSE, "TAG1", "variable 1 = %ld", var1);
Instrument the function in native library to tell you that it has been called.
For the first task you have to call the setText() method from native code. To do this, you have to call
env->CallVoidMethod(obj, mID);
where obj is the object you want to call the method on and mID is the method ID. The Void in the name suggests that the method returns void.
To get the method ID you have to call
jmethodID mID = env->GetMethodID(cls, "setText", "()V");
where cls is the class the method is part of, the first string is the method name and the second string is a method signature. In this case, the method takes no parameters ”()” and returns void “V”.
Finally, to get the class, call
jclass cls = env->FindClass("osp/lab5/tasks/MainActivity");
which has a single parameter, the class name and package, with the path divided by ”/”.
It is good practice to check for null when getting a class or methodID, and treating the error as required.
For the second task you have to call setText(String) method from native code. This is similar to the above task execept the method signature has changed, instead of ()V, the new method signature will be (Ljava/lang/String;)V. All parameters must have ; after them if they are objects. Objects are defined as L followed by the fully qualified class. Check the JNI Types Documentation.
To set the parameter, create a new jstring, and give it as a parameter after the methodID.
jstring str = env->NewStringUTF("OK"); env->CallVoidMethod(obj, mID, str);
Remember to release the string after you are done with it.
env->ReleaseStringUTFChars(str, env->GetStringUTFChars(str, NULL));
Call String getInput(). This method returns the content of the EditText box under of task 3. There are three things that need to change from the first task: the method name, the method signature, which will now be ()Ljava/lang/String; and the method call itself, which should be
jstring str = (jstring) env->CallObjectMethod(obj, mID);
Now, call setText(String, int), giving it as parameters the int that task3 receives and the jstring obtained from getInput. There is an easy way to obtain the signature of a function by using javap. Call it like this:
javap -classpath $WORKSPACE/Lab5_Tasks/app/build/intermediates/classes/flavor1/debug/osp/lab5/tasks -p -s osp.lab5.tasks.MainActivity
javap is used to disassemble java files, and the -s parameter is used to generate the signatures for public functions and fields, -p tells it to also generate signatures for private members of the class. Remember to call ReleaseStringUTFChars after you are done with the string.
This time, you have to call the getInt method and then check the result with checkInt. These are static methods. Accessing static methods is very similar to accessing instance methods. There are two changes: instead of using GetMethodID, use GetStaticMethodID, and when calling the function use CallStatic<Type>Method, passing the class instead of an object as first parameter.
Get value of the field “field” and return it. Getting the value of field means calling
jint i = env->GetIntField(obj, fID);
where obj is the object and fID is a jfieldID containing the ID of the field. To get the field ID, you need the class, obtained like in the previous exercises and to call
jfieldID fID = env->GetFieldID(cls, "field", "I");
where, cls is the jclass, “field” is the field name and “I” is the signature. Field signatures can also be obtained with javap (or by checking the JNI Types Documentation).
Set the value of the field “field” with the value received as a parameter. Instead of calling GetIntField, you will have to call
env->SetIntField(obj, fID, i);
with i being the value you wish to set the field to.
Generate an exception. To generate an exception you have to get the class of the exception and then call
env->ThrowNew(exception, "JNI Generated Exception");
where exception is the class of the exception. The string provides additional information.
Call the generateException method and check if it generated an exception. Return JNI_TRUE if yes, and JNI_FALSE otherwise. To check for an exception you can either call
jboolean ex = env->ExceptionCheck();
which returns JNI_TRUE if an exception has occurred and JNI_FALSE otherwise or
jthrowable ex = env->ExceptionOccured();
and check if ex is null or not. Remember to clear exception status, otherwise it will be thrown back to the JVM when the function returns.
env->ExceptionClear();
To access a Java array in the JNI you need a reference to it. You can pass the reference from Java or create a new array inside the JNI. Arrays have the type: j<type>Array, for example jintArray.
To create a Java array from JNI:
jintArray javaArray = env->NewIntArray(jsize length);
or
jobjectArray javaArray = env->NewObjectArray(jsize length, jclass elementClass, jobject initialElement);
where length is the number of elements in the array, elementClass is the class of the Objects and initialElement is the element each item in the array will be initialized as.
To access a primitive type array use:
env->Get<Type>ArrayRegion(j<type>Array array, jsize start, jsize len, j<type> *buf);
This will copy len elements, starting at element start from array into buf (starting from position 0 in buf).
To copy back from a native array use:
env->Set<Type>ArrayRegion(j<type>Array array, jsize start, jsize len, j<type> *buf);
This will copy len elements, starting at element start from buf into array (starting from position 0 in buf).
For object arrays, use
jobject obj = env->GetObjectArrayElement(jobjectArray array, jsize index); env->SetObjectArrayElement(jobjectArray array, jsize index, jobject obj);
instead. You cannot access more than one object at once (and it is impractical since there is a small amount of object references you can have at any one time).
Implement the two native functions: getIntArray and getStringArray. Allocate the arrays and initialize the elements to 1, 2, 3, 4, 5 and “one”, “two”, “three”, “four”, “five” respectively (from native code). They will get printed to the log. Use NewStringUTF to create the Strings. Return the created arrays. Use DeleteLocalRef to remove the String references (JNI guarantees only 16 references to Java objects at any one time).
In the getIntArray method, after creating the array, use
globalObj = env->NewGlobalRef(jobject obj)
to create a global reference. Make sure globalObj is declared globally. This means that globalObj will remain valid until it is deleted.
DeleteGlobalRef(globalObj);
deletes a global reference.
There are also Weak Global references, which allow objects to be garbage collected. You have to check if the reference is still valid before using it with IsSameObject.
Implement the updateArray method. Use the global reference to get access to the array. Increment each element by one. Use GetArrayLength to get the length of the array.
Get<Type>ArrayRegion and Set<Type>ArrayRegion copy the array each time they are used. This will add overhead. Use
j<type> *directArray = env->Get<Type>ArrayElements(j<type>Array array, jboolean *isCopy)
to get a direct reference (if possible).
To release the array and free space
env->Release<Type>ArrayElements(j<type>Array array, j<type> *directArray, jint mode)
where can be:
Copying is only done if directArray is a copy.
Modify the updateArray method to use direct copies of the array if possible.