Writing Native Code for Android Systems
Why ndk There exist large c++ code libraries – E.g., Audio and video compression, e.g., Ogg Vorbis, The LAME Project (mp3),.. OpenGL OpenSL ES – Low level audio Advanced CPU features – E.g., some ARM cpu support the NEON instruction set for signal and video processing
App are mixed java and c/c++ The java app is like a regular app. The java app is started by os. – It is not possible to have a stand alone c++ program (not sure, it might be) The c++ program is placed in a shared library – Shared libraries have names like libMyProgram.so The application package (.apk) will include the java app and the shared library jni (Java Native Interface) must be used to move data between java and c++
Outline of Steps Write c++ code in MyProject/jni Describe project sources in MyProject/jni/Android.mk – Like a make file, but much easier Build project by running the command../android-ndk-r5b/ndk-build from your MyProject directory – ndk-build is like make ndk-build – Builds Ndk-build clean – Cleans everything – Generates shared lib (libXX.so file) and places it in correct directory so the java program can get it Make.apk file by building app in eclipse – Important: whenever you make a change in the c++ program, of coruse, you need to run ndk-build. But, you also must rerun the java compile. To do this, make a trivial change in your java code and resave.
HelloJni Make new app called – Package: edu.udel.eleg454.HelloJni – Activity Name: HelloJni – Project: HelloJni Make new subdirectory in project call jni – i.e., HelloJni/jni In jni directory make new file called – MyHelloJni.cpp In this file, put – #include – extern "C" { – JNIEXPORT jstring JNICALL – Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, – jobject thiz ) – { – return env->NewStringUTF("Hello from JNI!"); – } Save file Important: function names must be exactly correct – Java_packageNameWithDotReplacedByUnderScore_JavaClassNameThatWillCallThisFunction_functionName
Android.mk In HelloJni/jni make new file called Android.mk Put the following in Android.mk – LOCAL_PATH := $(call my-dir) – include $(CLEAR_VARS) – LOCAL_MODULE := HelloJni – LOCAL_SRC_FILES := HelloJni.cpp – include $(BUILD_SHARED_LIBRARY) Note that LOCAL_MODULE is the module name Build library – Open terminal. – Cd dir to /HelloJni/jni – Run build /ndk-build – Check that libHelloJni.so is created
In java HelloJni After public class HelloJni extends Activity { – public native String stringFromJNI(); // the c++ function name – static { – System.loadLibrary("HelloJni"); // shared lib is called libHelloJni.so. // this name is from the LOCAL_MODULE part of the Android.mk file – } – Note: HelloJni is our In onCreate, after setContentView(R.layout.main); put – Log.e("debug","calling jni"); – Log.e("debug",stringFromJNI()); // last part of name of c++ function – Log.e("Debug","done"); Run and check log Note: public native … allows any function to be defined. But when this function is called, the shared library must have already been loaded (via System.loadLibrary)
play Change c++ function to be make string – Hello from JNI 2 Instead of – Hello from JNI! Rebuild and run from eclipse – Log does not show anything. Not even an error In eclipse make trivial change (delete and add ;) Run, and everything is ok
C++ Function name Change c++ function name. recompile and see error in LogCat – “no implementation found for native …” Make a new class called TestJni Move jni stuff into TestJni Run and see error Change function name from – Java_edu_udel_eleg454_helloJni_HelloJni_stringFrom JNI To – Java_edu_udel_eleg454_helloJni_TestJni_stringFromJ NI And runs ok
Logging from c++ In cpp file, add – #include In Android.mk add – LOCAL_LDLIBS := -llog In function add – __android_log_print(ANDROID_LOG_INFO, “DEBUG", “Here we are");
Passing strings from java to c++ with JNI In java code, make function arg include a string – Change public native String stringFromJNI(); – To public native String stringFromJNI(String name); – And change Log.e("debug",stringFromJNI()); – To Log.e("debug",stringFromJNI("string para")); In c++ code – Change JNIEXPORT jstring JNICALL Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz) – To JNIEXPORT jstring JNICALL Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz, jstring javaString ) – And add const char *str = env->GetStringUTFChars(javaString, 0); // convert java string to c++ str __android_log_print(ANDROID_LOG_INFO, "DEBUG", str); // do something env->ReleaseStringUTFChars(javaString, str); // release str Build, compile, run Note: after release, str is no longer valid
Passing int, floats, etc to c++ In java – Change public native String stringFromJNI(); – To public native String stringFromJNI(int val); – And change Log.e("debug",stringFromJNI()); – To int i = 100; Log.e("debug",stringFromJNI(i)); In c++ – Change JNIEXPORT jstring JNICALL Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz) – To JNIEXPORT jstring JNICALL Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz, jint ji ) – And comment out const char *str = env->GetStringUTFChars(javaString, 0); env->ReleaseStringUTFChars(javaString, str); – Add char str[80]; sprintf(str,"data is %d",ji); // be sure to add #include __android_log_print(ANDROID_LOG_INFO, "DEBUG", str); Build, compile, run
Jni Data types C++ type = jave type unsigned char = jboolean signed char = jbyte unsigned short = jchar Short = jshort Long = jlong Long long = jlong __int64 = jlong float = jfloat double = jdouble
Passing arrays of ints to c++ In java – Define function to take int array as argument Replace – public native String stringFromJNI(); With – public native String stringFromJNI(int[] val); – In onCreate Make array – int[] ints = new int[]{1,1,2,3,5,8,13}; Call function with ints as augment – Log.e("debug",stringFromJNI(ints));
Passing arrays of ints to c++ In c++ – Define function to take array as argument JNIEXPORT jstring JNICALL Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz, jintArray jiArray ) – Get size of array jsize arrayLength = env->GetArrayLength(jiArray); char str[80]; __android_log_print(ANDROID_LOG_INFO, "DEBUG", str); – Get pointer to array jint *data = env->GetIntArrayElements(jiArray, 0); – Do something with data for (int i=0; i<arrayLength; i++) { – sprintf(str,"val %d is %d",i,data[i]); – __android_log_print(ANDROID_LOG_INFO, "DEBUG", str); – data[i] = i; } – Release pointer env->ReleaseIntArrayElements(jiArray, data, 0); Build, compile, run
More passing arrays to c++ env->ReleaseIntArrayElements(jiArray, data, 0); – Last argument is 0 => data is copied back to java and java can delete data array – Last argument is JNI_COMMIT => data is copied back, but java should not delete the array – Last argument is JNI_ABORT => data is not copied back and java can delete Check if the data was changed in c++ – In java, after Log.e("debug",stringFromJNI(ints)); add for (int i=0; i<ints.length; i++) { Log.e("DEBUG","ret val["+i+"] = "+ints[i]); } – run, and see that ints is updated
Returning data Java – define function to return int public native int stringFromJNI(int[] val); – Call function and print return value Log.e("debug","results = "+stringFromJNI(ints)); C++ – Change function prototype to return jint JNIEXPORT jint JNICALL Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz, jintArray jiArray ) – return int return 12; Build, compile, run
Return arrays Same as returning a string but use NewIntArray