AndroidNDK mobile development

Android NDK passing complex data not so scary anymore

The Android NDK is a toolset that lets you implement parts of your app using native-code languages such as C and C++. For certain types of apps, this can help you reuse code libraries written in those languages.

To configure Your environment I strongly recommend  using Experimental Gradle Build. You can find more info about it under these links:
http://tools.android.com/tech-docs/new-build-system/gradle-experimental/

and also a handy tutorial to build UR first project:
https://codelabs.developers.google.com/codelabs/android-studio-jni/

Passing data by arguments and return statement
Let’s start with a simple example how to pass String from C to Java:

 

Java code:

...
private void callJniMethod(){
 String msg = getMsgFromJni();

Log.d(TAG, msg);
 }

static {
 System.loadLibrary("foo");
 }
public native String getMsgFromJni();
 } //FooActivity

C code

...

JNIEXPORT jstring JNICALL
 Java_com_foo_sample_FooActivity_getMsgFromJni(JNIEnv *env, jobject obj) {
 return (*env)->NewStringUTF(env, "Hello World NDK");
 }

 

Java Type
Native Type
java.lang.Class
jclass
java.lang.Object
jobject
java.lang.String
jstring
object array
jobjectArray
boolean array
jbooleanArray
char array
jcharArray
short array
jshortArray
int array
jintArray
long array
jlongArray
float array
jfloatArray
double array
jdoubleArray

 

To pass primitive types we just need to know the equivalent from the Native side and use it properly. Below You can find a table with those equivalents.

 

Java Type
Native Type
Description
Boolean
jboolean
unsigned 8 bits
byte
jbyte
signed 8 bits
char
jchar
unsigned 16 bits
short
jshort
signed 16 bits
int
jint
signed 32 bits
long
jlong
signed 64 bits
float
jfloat
32 bits
double
jdouble
64 bits
void
void
N/A

 

Primitive Types and Native Equivalents

Reference types that correspond to different kinds of Java objects.

 

The following table summarizes the encoding for the Java type signatures:

Signature
Java Type
Z
boolean
B
byte
C
char
S
short
I
int
J
long
F
float
D
double
L fully-qualified-class;
fully-qualified-class
[ type
type[]
( arg-types ) ret-type
method type

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

It’s important to remember when passing arrays objects such as jdoubleArray, jstring and so on. Firstly we need to obtain the  array element pointers.

In general, the garbage collector may move Java arrays. However, the Java Virtual Machine guarantees that the result of Get<type>ArrayElements points to a nonmovable array of integers. The JNI will either “pin” down the array or it will make a copy of the array into nonmovable memory. Thanks to this,  the native code must call Release<type>ArrayElements when it has finished using the array, as follows:

Release<type>ArrayElements enables the JNI to copy back and free the memory referenced by the body parameter if it is a copy of the original Java array. The “copy back” action enables the calling program to obtain the new values of array elements that the native method may have modified. Release<type>ArrayElements will “unpin” the Java array if it has already been pinned in memory.

 

Java code

...

  private void callJniMethod(){
    double [] tmpArrayLeft = {1, 2, 3};
    double [] tmpArrayRight = {4, 5, 6};
    int tmpIntValue = 1;
    float tmpFloatValue = 2.3f;
    passingDataToJni(tmpArrayLeft, tmpArrayRight, tmpIntValue, tmpFloatValue);
}

  static {
    System.loadLibrary("foo");
 }

  public native void passingDataToJni(double[] doubleLeftArray, double[] doubleRightArray,
  int intValue, String stringValue);

} //FooActivity

 

Code C

...

JNIEXPORT void JNICALL
 Java_com_foo_sample_FooActivity_passingDataToJni(JNIEnv *env, jobject instance,
 jdoubleArray doubleLeftArray_,
 jdoubleArray doubleRightArray_,
 jint intValue, jstring stringValue_) {

  jdouble *doubleLeftArray = (*env)->GetDoubleArrayElements(env, doubleLeftArray_, NULL);
  jdouble *doubleRightArray = (*env)->GetDoubleArrayElements(env, doubleRightArray_, NULL);

  const jchar *stringValue = (*env)->GetStringChars(env, stringValue_, 0);
  const char *stringValueUTF = (*env)->GetStringUTFChars(env, stringValue_, 0);
// TODO

 (*env)->ReleaseDoubleArrayElements(env, doubleLeftArray_, doubleLeftArray, 0);
 (*env)->ReleaseDoubleArrayElements(env, doubleRightArray_, doubleRightArray, 0);
 (*env)->ReleaseStringUTFChars(env,  stringValue_, stringValueUTF);
 (*env)->ReleaseStringChars(env,  stringValue_, stringValue);
 }

 

Note that the Get<type>ArrayElements function might potentially copy the entire array. You may want to limit the number of elements that are copied, especially if your array is large. If you are only interested in a small number of elements in a (potentially) large array, you should instead use the Get/Set<type>ArrayRegion functions. These functions allow you to access, via copying, a small set of elements in an array.

  1. Passing and returning complex data like C structures, Java objects

The NDK provides functions that native methods use to get and set Java member variables. You can get and set both instance and class member variables. Similar to accessing methods, you use one set of NDK functions to access instance member variables and another set of NDK functions to access class member variables.

 

public class FooModel {
   float fooFloat;
   long fooLong;
   int fooInt;
   String fooString;

...

//getters and setters
 }

...

  private void callJniMethod(){
    FooModel fooModel = new FooModel();
    passingJavaObject(fooModel);
    Log.d(TAG, fooModel.getFooString());
 }

  static {
    System.loadLibrary("foo");
 }

   public native void passingJavaObject(FooModel fooModel);

} //FooActivity
...

JNIEXPORT void JNICALL
Java_com_foo_sample_FooActivity_passingJavaObject(JNIEnv *env, jobject instance,
 jobject fooModel_) {

jclass fooModel = (*env)->GetObjectClass(env, fooModel_);
  jfieldID fooFloat = (*env)->GetFieldID(env, fooModel, "fooFloat", "D");
  jfieldID fooLong = (*env)->GetFieldID(env, fooModel, "fooLong", "J");
  jfieldID fooInt = (*env)->GetFieldID(env, fooModel, "fooInt", "I");
  jfieldID fooString = (*env)->GetFieldID(env, fooModel, "fooString", "Ljava/lang/String;");

  (*env)->SetFloatField(env, fooModel_ , fooFloat, 1.1f);
  (*env)->SetLongField(env, fooModel_ , fooLong, 1);
  (*env)->SetIntField(env, fooModel_ , fooInt, 1);
  (*env)->SetObjectField(env, fooModel_ , fooString, (*env)->NewStringUTF(env, "foo"));
 }

 

 

Another way is to create a Java object directly in C code, setup his fields and then return it. Where in FindClass function we pass as second argument the path to class. In GetMehodID second argument is a class instance for the method, the third one is “<init>” which it means we will be calling the constructor, and the fourth argument is what parameters will take constructor. In our example constructor  is not taking any arguments so we pass an empty bracket.

 

...

JNIEXPORT jobject JNICALL
Java_com_foo_sample_FooActivity_passingJavaObject(JNIEnv *env, jobject instance) {

    jclass fooModelClass = (*env)->FindClass(env, "com/foo/sample/FooModel");
    jmethodID methodId = (*env)->GetMethodID(env, fooModelClass, "<init>", "()V");
    jobject fooModel = (*env)->NewObject(env, cls, methodId);

    //getters and setters
    return fooModel;
 }

 

Calling Java method from C/C++ and passing data as they arguments.

This section illustrates how to call Java methods from native language methods. The GetMethodID takes as second parameter class where the method will be found, as third parameter name of the method and the fourth parameter is what kinds of arguments does a method take and what it will return.

 

C code

...

JNIEXPORT void JNICALL
Java_com_foo_sample_FooActivity_passingJavaObject(JNIEnv *env, jobject instance) {

    jmethodID callbackJava = (*env)->GetMethodID(env, instance, "callback", "([D[DILjava/lang/String;)V");
    if (NULL == callbackJava) return;

    // Your code withe 2 double arrays, int and String to pass

   (*env)->CallVoidMethod(env, instance, callbackJava, doubleArrayLeft, doubleArrayRight, intValue, stringValue);
}

 

Java code

...

  private void callback(double[] left, double[] right, int intValue, String stringValue) {
     Log.d(TAG, Arrays.toString(left));
     Log.d(TAG, Arrays.toString(right));
     Log.d(TAG, String.valueOf(intValue));
     Log.d(TAG, stringValue);
 }

 static {
    System.loadLibrary("foo");
 }

 public native void callJavaMethod();

} //FooActivity

 

Personally, I found it much easier to pass primitive types and arrays of primitive typed from NDK to Java. Object creation/access is messy and hard to debug but sometimes it’s the only way for a much cleaner and transparent code.