Writing Native Code for Android Systems Why ndk

  • Slides: 30
Download presentation
Writing Native Code for Android Systems

Writing Native Code for Android Systems

Why ndk (native developers kit) • There exist large c/c++ code libraries – E.

Why ndk (native developers kit) • There exist large c/c++ code libraries – E. g. , Audio and video compression, e. g. , Ogg Vorbis, The LAME Project (mp 3), . . • Open. GL • Open. SL ES – Low level audio • Advanced CPU features – E. g. , some ARM cpus support the NEON instruction set for signal and video processing

 • Apps that use the NDK are typically mixed java and c/c++ –

• 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 lib. My. Program. 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 • Goto http: //developer. android. com/sdk/ndk/index. html and get NDK –

Outline of Steps • Goto http: //developer. android. com/sdk/ndk/index. html and get NDK – • • Make regular java project, e. g. , My. Project Make subdirectory, My. Project/jni Write c++ code in My. Project/jni Describe project sources in My. Project/jni/Android. mk – • Build project by running the command. . /android-ndk-r 7/ndk-build from your My. Project/jni directory Windows – • Like a make file, but much easier Linux and MAC – • Note: the current version of the ndk works well in windows. Previous versions needed cygwin Build project by running the command c: android-ndk-r 7 bndk-build from your My. Projectjni directory ndk-build is like make – ndk-build • – ndk-build clean • – • Builds Cleans everything Generates shared lib (lib. XX. 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

Hello. Jni • Make new app called – – – • Make new subdirectory

Hello. Jni • Make new app called – – – • Make new subdirectory in project call jni – • • • i. e. , Hello. Jni/jni In jni directory make new file called – • Package: edu. udel. eleg 454. Hello. Jni Activity Name: Hello. Jni Project: Hello. Jni My. Hello. Jni. cpp In this file, put – – – – – #include <string. h> #include <jni. h> extern "C" { JNIEXPORT jstring JNICALL Java_edu_udel_eleg 454_Hello. Jni. Activity_string. From. JNI( JNIEnv* env, jobject thiz ) { return env->New. String. UTF("Hello from JNI!"); } – } Save file Important: function names must be exactly correct – Java_package. Name. With. Dot. Replaced. By. Under. Score_Java. Class. Name. That. Will. Call. This. Function_function. Name

Android. mk • In Hello. Jni/jni make new file called Android. mk • Put

Android. mk • In Hello. Jni/jni make new file called Android. mk • Put the following in Android. mk – LOCAL_PATH : = $(call my-dir) – include $(CLEAR_VARS) – LOCAL_MODULE : = Hello. Jni – LOCAL_SRC_FILES : = My. Hello. Jni. cpp – include $(BUILD_SHARED_LIBRARY) • Note that LOCAL_MODULE defines the module name • Build library – Open terminal. – Cd dir to <workspace>/Hello. Jni/jni – Run build • <android-ndk-r 7 b>/ndk-build – Check that lib. Hello. Jni. so is created

In java Hello. Jni • After public class Hello. Jni. Activity extends Activity {

In java Hello. Jni • After public class Hello. Jni. Activity extends Activity { – public native String string. From. JNI(); // the c++ function name – static { – System. load. Library("Hello. Jni"); // shared lib is called lib. Hello. Jni. so. • // this name is from the LOCAL_MODULE part of the Android. mk file – } • In on. Create, after set. Content. View(R. layout. main); put – Log. e("debug", "calling jni"); – Log. e("debug", string. From. JNI()); // 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. load. Library)

play • Change c++ function to be make string – Hello from JNI 2

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

C++ Function name • Change c++ function name. recompile and see error in Log. Cat – “no implementation found for native …” • • Make a new class called Test. Jni Move jni stuff into Test. Jni Run and see error Change function name from – Java_edu_udel_eleg 454_Hello. JNIActivity_string From. JNI • To – Java_edu_udel_eleg 454_hello. JNI_Test. Jni_string. From. JNI • And runs ok

Logging from c++ • In cpp file, add – #include <android/log. h> • In

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");

Passing strings from java to c++ with JNI • In java code, make function

Passing strings from java to c++ with JNI • In java code, make function arg include a string – Change • public native String string. From. JNI(); – To • public native String string. From. JNI(String name); – And change • Log. e("debug", string. From. JNI()); – To • Log. e("debug", string. From. JNI("string para")); • In c++ code – Change • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg 454_hello. Jni_Hello. Jni_string. From. JNI( JNIEnv* env, • jobject thiz) – To • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg 454_hello. Jni_Hello. Jni_string. From. JNI( JNIEnv* env, • jobject thiz, jstring java. String ) – And add • const char *str = env->Get. String. UTFChars(java. String, 0); // convert java string to c++ str • __android_log_print(ANDROID_LOG_INFO, "DEBUG", str); // do something • env->Release. String. UTFChars(java. String, 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

Passing int, floats, etc to c++ • In java – Change • public native String string. From. JNI(); – To • public native String string. From. JNI(int val); – And change • Log. e("debug", string. From. JNI()); – To • In c++ • int i = 100; • Log. e("debug", string. From. JNI(i)); – Change • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg 454_hello. Jni_Hello. Jni_string. From. JNI( JNIEnv* env, • jobject thiz) – To • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg 454_hello. Jni_Hello. Jni_string. From. JNI( JNIEnv* env, • jobject thiz, jint ji ) – And comment out • const char *str = env->Get. String. UTFChars(java. String, 0); • env->Release. String. UTFChars(java. String, 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 =

Jni Data types • • • C++ type = jave type unsigned char = jboolean signed char = jbyte unsigned short = jchar Short = jshort Long = jlong Long long = jlong __int 64 = jlong float = jfloat double = jdouble

Passing arrays of ints to c++ • In java – Define function to take

Passing arrays of ints to c++ • In java – Define function to take int array as argument • Replace – public native String string. From. JNI(); • With – public native String string. From. JNI(int[] val); – In on. Create • Make array – int[] ints = new int[]{1, 1, 2, 3, 5, 8, 13}; • Call function with ints as augment – Log. e("debug", string. From. JNI(ints));

Passing arrays of ints to c++ • In c++ – Define function to take

Passing arrays of ints to c++ • In c++ – Define function to take array as argument • JNIEXPORT jstring JNICALL • Java_edu_udel_eleg 454_hello. Jni_Hello. Jni_string. From. JNI( JNIEnv* env, • jobject thiz, jint. Array ji. Array ) – Get size of array • jsize array. Length = env->Get. Array. Length(ji. Array); – Get pointer to array • jint *data = env->Get. Int. Array. Elements(ji. Array, 0); – Do something with data • char str[80]; • for (int i=0; i<array. Length; i++) { – – – • } sprintf(str, "val %d is %d", i, data[i]); __android_log_print(ANDROID_LOG_INFO, "DEBUG", str); data[i] = i; – Release pointer • env->Release. Int. Array. Elements(ji. Array, data, 0); • Build, compile, run

More passing arrays to c++ • env->Release. Int. Array. Elements(ji. Array, data, 0); –

More passing arrays to c++ • env->Release. Int. Array. Elements(ji. Array, 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", string. From. JNI(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

Returning data • Java – define function to return int • public native int string. From. JNI(int[] val); – Call function and print return value • C++ • Log. e("debug", "results = "+string. From. JNI(ints)); – Change function prototype to return jint • JNIEXPORT jint JNICALL • Java_edu_udel_eleg 454_hello. Jni_Hello. Jni_string. From. JNI( JNIEnv* env, • jobject thiz, jint. Array ji. Array ) – return int • return 12; • Build, compile, run

Return arrays • Same as returning a string but use New. Int. Array

Return arrays • Same as returning a string but use New. Int. Array

SWIG • SWIG automatically builds an interface between java and c/c++ – SWIG constructs

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/Doc 1. 3/Java. html • Section 5 of http: //www. swig. org/Doc 2. 0/SWIGDocumentation. pdf • Swig 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 • Linux • Decompress and get swig. exe and paste swig. exe into D: SWIG – apt-get install swig

 • • New app, Test. Swig Make jni directory – – • right

• • New app, Test. Swig Make jni directory – – • right click on Test. Swig 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 Test. Swig and select Refresh Either way, Jni must appear in list of directories, • otherwise this process will fail Make test. Swig. c – – – • • You can make this in eclipse package explorer • • • – Swig 1 Could be Test. Swig. cpp, but there is a minor difference later #include <string. h> #include <jni. h> #include <android/log. h> #include <stdio. h> int test. This(double val) { char str[128]; sprintf(str, "Here we are: %f", val); __android_log_print(ANDROID_LOG_INFO, "Test. Swig", str); return 12; } Make Test. Swig. 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 int test. This(double val); • Make interface file called Test. Swig. i and save in jni directory. This defines the interface between java and c/c++ – – – %module test. Swig %{ #include "Test. Swig. h" %} extern int test. This(double val); Get ready to run swig – – Make directory Test. Swigsrceduudeleleg 454Swig Run swig – – – Open command prompt Change to Test. Swigjni directory execute • – – – D: swig. exe –java –package edu. udel. eleg 454. Swig – outdir. . /src/edu/udel/eleg 454/Swig test. Swig. 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

Import to eclipse • Import – – – • The first import is a

Import to eclipse • Import – – – • The first import is a bit odd. After the first time, everything runs smoothly Open file explorer and browse to Test. Swigsrceduudeleleg 454 Cut Swig directory • – – – – – i. e. , the directory with all the java files we just made Paste to d: temptest. Swigeduudeleleg 454Swig • • – – Now, the next time you run swig. exe …. The files will be updated. you need to make this directory Make sure that you cut, i. e. , that Swig directory is deleted from Test. Swigsrceduudeleleg 454 Go Back to eclipse In project explorer, right click on Test. Swig/src (the project) Select import Under General is File System , select File System. Next From Directory: Browse to d: temp and select test. Swig. OK Select the check box next to edu Set Into folder to Test. Swig/src Click finish Check that under /src is a new package edu. udel. eleg 454. Swig • 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/eleg 454/Swig and other times, edu. udel. eleg 454. Swig. We must have edu. udel. eleg 454. Swig Advanced/optional: – – – You can make swig. exe run automatically In package explores, right click on project, Test. Swig -> properties->Builders-> new-> program click ok Name: swig Location: d: swig. exe Work directory, ${project_loc}/jni Augments: • –java –package edu. udel. eleg 454. Swig –outdir edu/udel/eleg 454/Swig test. Swig. i

NDK • Android. mk – LOCAL_PATH : = $(call my-dir) – include $(CLEAR_VARS) –

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

java • Now we can use this nice interface in our java code •

java • Now we can use this nice interface in our java code • In Test. Swig. Activity, after public class Test. Swig. Activity extends Activity {, add – static { – System. load. Library("Test. Swig"); – } – 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 on. Create, after set. Content. View(), add – Log. e("test. Swig", "test: "+test. Swig. test. This(2)); • test. Swig is the class, and test. This is the c/c++ function

Structures • In Test. Swig. h add – struct Test. Struct { – };

Structures • In Test. Swig. h add – struct Test. Struct { – }; • int a; • double b; • In Test. Swig. i, after extern int test. This(double val); add – struct Test. Struct { – }; • 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 Test. Swig_wrap. c has a. h where the struct is defined • Run swig as before • Swig will generate class Test. Struct and Test. Struct. java with operators – get. A(), get. B(), set. A(int a), set. B(double b) – To use Test. Struct in java, Test. Struct test. Struct = new Test. Struct(); • 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

classes • Make Test. Swig. Class. h – – class Test. Swig. Class {

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

Extending structures/classes: motivation • A key goal of SWIG is that the c/c++ code

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 to. String – E. g. , the c/c++ interface does not fit well into java, so a new interface should be made

Extending classes/structures • A new class can be defined in the. i file and

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

Extending classes/structures • Consider the Test. Swig. Class made before. • Goal: extend to

Extending classes/structures • Consider the Test. Swig. Class made before. • Goal: extend to include add fucntion • In swig. i, add – %extend Test. Swig. Class { • void add(class Test. Swig. Class *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 Test. Swig. Class (that would require changing Test. Swig. Class). • Instead, the following is generated – SWIGINTERN void Test. Swig. Class_add(Test. Swig. Class *self, Test. Swig. Class *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(Test. Swig. Class other) { …} • One drawback of extending classes is that your extension code might have bugs and then debugging requires examining test. Swig_wrap. cxx

Making a new interface class/structure • Instead of only extending an existing class/structure, you

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 My. Interface. Class – – In test. Swig. i, At the top, in the %module test. Swig section, add • • class My. Interface. Class { public: – – • – }; Also, after the %module test. Swig section, add (yes, you must add the same stuff in two places) • • class My. Interface. Class { public: – – • – }; int a; double b; After class My. Interface. Class, add • %extend My. Interface. Class { – – • • int a; double b; }; My. Interface. Class() { » My. Interface. Class *u = new My. Interface. Class(); » 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.

stl: : vector, stl: : string, … • Much of stl can be accommodated

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