1 / 30

Writing Native Code for Android Systems

Learn why NDK (Native Developers Kit) is useful for Android development, how to use C/C++ code libraries, and how to interface with Java using JNI.

rmazzarella
Download Presentation

Writing Native Code for Android Systems

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Writing Native Code for Android Systems

  2. Why ndk (native developers kit) • There exist large c/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 cpus support the NEON instruction set for signal and video processing

  3. Apps that use the NDK are typically mixed java and c/c++ • Pure c/c++ apps are possible • The java app is like a regular app. The java app is started by os. • The c/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++

  4. Outline of Steps • Gotohttp://developer.android.com/sdk/ndk/index.html and get NDK • Note: the current version of the ndk works well in windows. Previous versions needed cygwin • Make regular java project, e.g., MyProject • Make subdirectory, MyProject/jni • Write c++ code in MyProject/jni • Describe project sources in MyProject/jni/Android.mk • Like a make file, but much easier • Linux and MAC • Build project by running the command ../android-ndk-r7/ndk-build from your MyProject/jni directory • Windows • Build project by running the command c:\android\android-ndk-r7b\ndk-build from your MyProject\jni 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 the app in eclipse • Important: whenever you make a change in the c++ program, of course, 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. Or run clean project from eclipse

  5. 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_HelloJniActivity_stringFromJNI( JNIEnv* env, • jobjectthiz ) • { • return env->NewStringUTF("Hello from JNI!"); • } • } • Save file • Important: function names must be exactly correct • Java_packageNameWithDotReplacedByUnderScore_JavaClassNameThatWillCallThisFunction_functionName

  6. 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 := MyHelloJni.cpp • include $(BUILD_SHARED_LIBRARY) • Note that LOCAL_MODULE definesthe module name • Build library • Open terminal. • Cd dir to <workspace>/HelloJni/jni • Run build • <android-ndk-r7b>/ndk-build • Check that libHelloJni.so is created

  7. In java HelloJni • After public class HelloJniActivity 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 • } • 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)

  8. 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

  9. 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_HelloJNIActivity_stringFromJNI • To • Java_edu_udel_eleg454_helloJNI_TestJni_stringFromJNI • And runs ok

  10. Logging from c++ • In cpp file, add • #include <android/log.h> • In Android.mk, after include $(CLEAR_VARS) add, • LOCAL_LDLIBS := -llog • In function add • __android_log_print(ANDROID_LOG_INFO, "DEBUG", "Here we are");

  11. 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

  12. 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

  13. 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

  14. 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));

  15. 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); • Get pointer to array • jint *data = env->GetIntArrayElements(jiArray, 0); • Do something with data • char str[80]; • 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

  16. 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

  17. 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

  18. Return arrays • Same as returning a string but use NewIntArray

  19. SWIG • SWIG automatically builds an interface between java and c/c++ • SWIG constructs an interface that is composed of java and c/c++ code. The result is that your java code and your c/c++ code is simpler • you don’t need to mess with JNI • You don’t need to change the basic c++ code to support java. Just use swig to make an interface • Swig also for interfaces between c/c++ and many other scripting languages • More info • on swig and java: http://www.swig.org/Doc1.3/Java.html • Section 5 of http://www.swig.org/Doc2.0/SWIGDocumentation.pdf • Swig and android: http://swig.svn.sourceforge.net/viewvc/swig/trunk/Doc/Manual/Android.html • Swig is a bit time consuming to get set up, but if you are working with a complicated interface between java and c/c++, it is worth it. • I have found jni is difficult to work with. We already saw some problems with how difficult the naming is. And there are other complications • Windows set up • http://www.swig.org/download.html • Download full version of SWIG (not the windows executable version) • Decompress into D:\SWIG • Download windows executable version • Decompress and get swig.exe and paste swig.exe into D:\SWIG • Linux • apt-get install swig

  20. Swig 1 • New app, TestSwig • Make jni directory • You can make this in eclipse package explorer • right click on TestSwig • New->Folder: Folder name: jni • Check that jni appears between bin and res • Otherwise, make it in file explorer. Then, in eclipse package explorer, right click TestSwig and select Refresh • Either way, Jni must appear in list of directories, otherwise this process will fail • Make testSwig.c • Could be TestSwig.cpp, but there is a minor difference later • #include <string.h> • #include <jni.h> • #include <android/log.h> • #include <stdio.h> • inttestThis(double val) { • char str[128]; • sprintf(str,"Here we are: %f",val); • __android_log_print(ANDROID_LOG_INFO, "TestSwig", str); • return 12; • } • Make TestSwig.h • A .h file is needed to the interface. Any function that is aprt of the interfaces (i.e., can be called from java) must be in a .h file • inttestThis(double val); • Make interface file called TestSwig.i and save in jni directory. This defines the interface between java and c/c++ • %module testSwig • %{ • #include "TestSwig.h" • %} • extern inttestThis(double val); • Get ready to run swig • Make directory • TestSwig\src\edu\udel\eleg454\Swig • Run swig • Open command prompt • Change to TestSwig\jni directory • execute • D:\swig\swig.exe –java –package edu.udel.eleg454.Swig –outdir ../src/edu/udel/eleg454/Swig testSwig.i • -package will make this interface a package • -outdir defines the directory. This directory must be made before running swig • Check • Check that java files are made in

  21. Import to eclipse • Import • The first import is a bit odd. After the first time, everything runs smoothly • Open file explorer and browse to TestSwig\src\edu\udel\eleg454 • Cut Swig directory • i.e., the directory with all the java files we just made • Paste to d:\temp\testSwig\edu\udel\eleg454\Swig • you need to make this directory • Make sure that you cut, i.e., that Swig directory is deleted from TestSwig\src\edu\udel\eleg454 • Go Back to eclipse • In project explorer, right click on TestSwig/src (the project) • Select import • Under General is File System , select File System. Next • From Directory: Browse to d:\temp and select testSwig. OK • Select the check box next to edu • Set Into folder to TestSwig/src • Click finish • Check that under /src is a new package edu.udel.eleg454.Swig • Now, the next time you run swig.exe …. The files will be updated. • The reason we had to move them to \temp is that eclipse does not let you import to the location where the files are. • I don’t understand why sometime when you import the eclipse package explorer shows you edu/udel/eleg454/Swig and other times, edu.udel.eleg454.Swig. We must have edu.udel.eleg454.Swig • Advanced/optional: • You can make swig.exe run automatically • In package explores, right click on project, TestSwig -> properties->Builders-> new-> program click ok • Name: swig • Location: d:\swig\swig.exe • Work directory, ${project_loc}/jni • Augments: • –java –package edu.udel.eleg454.Swig –outdiredu/udel/eleg454/Swig testSwig.i

  22. NDK • Android.mk • LOCAL_PATH := $(call my-dir) • include $(CLEAR_VARS) • LOCAL_LDLIBS := -llog • LOCAL_MODULE := TestSwig • LOCAL_SRC_FILES := testSwig.ctestSwig_wrap.c • include $(BUILD_SHARED_LIBRARY) • testSwig.c is our c code • testSwig_wrap.c is generated by swig • Don’t attempt to read testSwig_wrap.c. It is not meant to be read or understood

  23. java • Now we can use this nice interface in our java code • In TestSwigActivity, after public class TestSwigActivity extends Activity {, add • static { • System.loadLibrary("TestSwig"); • } • It is too bad that you need to add this manually. I think swig should add it to the java files automatically. But it does not, so you need to add it here • In onCreate, after setContentView(), add • Log.e("testSwig","test: "+testSwig.testThis(2)); • testSwig is the class, and testThis is the c/c++ function

  24. Structures • In TestSwig.h add • structTestStruct { • int a; • double b; • }; • In TestSwig.i, after extern inttestThis(double val); add • structTestStruct { • int a; • double b; • }; • Note that the struct must be defined in both places. • It is defined in .i so swig makes an interface • It is defined in .h so that TestSwig_wrap.c has a .h where the struct is defined • Run swig as before • Swig will generate class TestStruct and TestStruct.java with operators • getA(), getB(), setA(int a), setB(double b) • To use TestStruct in java, TestStructtestStruct = new TestStruct(); • The extra java files can be added to the project as before • Cut them from the directory to some other directory. Then drag the file from file explorer to eclipse package explorer

  25. classes • Make TestSwigClass.h • class TestSwigClass { • public: • int a; • double b; • void set_b(double _b) { b = _b; }; • intget_a() {return a;} • TestSwigClass() {}; • void process() { • // nothing yet • } • }; • At the top of TestSwig.i, add TestSwigClass.h to module section, so it looks like • %module testSwig • %{ • #include "testSwig.h" • #include "TestSwigClass.h" • %} • At the end of TestSwig.i, add the contents of TestSwigClass.h • Running swig for c++ code is slightly different than running for c code • D:\swig\swig.exe –java –c++ –package edu.udel.eleg454.Swig –outdir ../src/edu/udel/eleg454/Swig –o testSwig_wrap.cpp testSwig.i • This will generate a testSwig_wrap.cxx file, not testSwig_wrap.c • Android ndk does not accept .cxx files. So you need to add –o testSwig_wrap.cpp to make the correct output file • Note: mixing .c and .cpp is a bad idea. So rename TestSwig.c to TestSwig.cpp and update Android.mk • This will generate even more java files that need to be added to your project

  26. Extending structures/classes: motivation • A key goal of SWIG is that the c/c++ code does not need to be changed to support the java interface • SWIG generates a interface code in c/c++ and several in java • However, sometimes simply “porting” the c/c++ interface to java is insuffucient • E.g., some c/c++ functions return data in the arguments, which might not be compatible with java • E.g., java makes functions that c/c++ does not, such as toString • E.g., the c/c++ interface does not fit well into java, so a new interface should be made

  27. Extending classes/structures • A new class can be defined in the .i file and these or any class can be extended • The result of an extension is that the java interface appears as it would if the c/c++ interface had these changes. • However, the c/c++ code is not changed

  28. Extending classes/structures • Consider the TestSwigClassmade before. • Goal: extend to include add fucntion • In swig.i, add • %extend TestSwigClass{ • void add(class TestSwigClass*other) { • self->a = self->a + other->a; • self->b = self->b + other->b; • } • }; • Note that self is like this. When this function is generate, it is not a member function of TestSwigClass (that would require changing TestSwigClass). • Instead, the following is generated • SWIGINTERN void TestSwigClass_add(TestSwigClass *self, TestSwigClass*other){ • self->a = self->a + other->a; • self->b = self->b + other->b; • } • Note that self is automatically added as a parameter, this you can use the same way you use this. • The java interface is • public void add(TestSwigClass other) { …} • One drawback of extending classes is that your extension code might have bugs and then debugging requires examining testSwig_wrap.cxx

  29. Making a new interface class/structure • Instead of only extending an existing class/structure, you might want to make an entire new class • Again, this class should only be implemented in the interface code generated by swig • E.g., make class MyInterfaceClass • In testSwig.i, • At the top, in the %module testSwig section, add • class MyInterfaceClass { • public: • inta; • double b; • }; • Also, after the %module testSwigsection, add (yes, you must add the same stuff in two places) • class MyInterfaceClass { • public: • inta; • double b; • }; • After class MyInterfaceClass, add • %extend MyInterfaceClass{ • MyInterfaceClass() { • MyInterfaceClass*u = new MyInterfaceClass(); • u->a = 0; • u->b= 0; • return u; • } • }; • Clearly, if you need to make more than a small number of interface classes, you should just make them in c/c++ yourself and not rely on swig.

  30. stl::vector, stl::string, … • Much of stl can be accommodated • See section 8.4 in the swig documentation • However, my install was missing stl_list.i, so I could not get list to work. Vector and string did work

More Related