180 likes | 359 Views
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 O penSL ES Low level audio Advanced CPU features
E N D
Why ndk • There exist large c++ code libraries • E.g., Audio and video compression, e.g., OggVorbis, 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 <string.h> • #include <jni.h> • extern "C" { • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, • jobjectthiz ) • { • 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 <workspace>/HelloJni/jni • Run build • <android-ndk-r5b>/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","callingjni"); • 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_stringFromJNI • To • Java_edu_udel_eleg454_helloJni_TestJni_stringFromJNI • And runs ok
Logging from c++ • In cpp file, add • #include <android/log.h> • 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, • jobjectthiz) • To • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, • jobjectthiz, jstringjavaString ) • 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(intval); • And change • Log.e("debug",stringFromJNI()); • To • inti = 100; • Log.e("debug",stringFromJNI(i)); • In c++ • Change • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, • jobjectthiz) • To • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg454_helloJni_HelloJni_stringFromJNI( JNIEnv* env, • jobjectthiz, jintji ) • 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 <stdio.h> • __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, • jobjectthiz, jintArrayjiArray ) • Get size of array • jsizearrayLength = 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 (inti=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 intstringFromJNI(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, • jobjectthiz, jintArrayjiArray ) • return int • return 12; • Build, compile, run
Return arrays • Same as returning a string but use NewIntArray