JNI Part 2: Examples

Hello everyone My name is Roman Aimaletdinov, I am developing a Citymobil client application. I continue my series of articles on JNI, as the technology is rarely used, but sometimes it is very useful (or just interesting). This time I will show examples of solutions in JNI, which are quite a bit more complicated than hello world. And if you are not familiar with JNI, then I advise you to start with the first part.

Content

  • JNI types.
  • Return in the native method.
  • How to transmit List<List>.
  • How to go through the loop in native.
  • Calling a Java method from C++.

JNI types

In JNI, we are forced to use special types for the native environment. We can’t just hand it over int and work with it in C++, although it seems logical. Thus, the types existing in Java for JNI are duplicated with the prefix j. For example:

  • boolean → jboolean
  • byte → jbyte
  • char → jchar

And the matter is not limited to primitive types alone, as described in the Oracle documentation. You will also have to deal with some inconveniences, i.e. transformations.

How to work with this — we will consider in the next chapter.

Return in the native method

  1. In the AwesomeLib file, which contains our native methods, we will create a method getRandom, which returns int.
public class AwesomeLib {
  
    static {
        System.loadLibrary("nativeLib");
    }

    public native void helloHabr();

    public native int getRandom(); // <- new method
}
  1. Then we generate the header with the command javac -h . AwesomeLib.java, our file .h it will be updated and a new method will appear:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class nativelib_AwesomeLib */
#ifndef _Included_nativelib_AwesomeLib
#define _Included_nativelib_AwesomeLib
#ifdef __cplusplus
extern "C" {
#endif
/*
    Class:     nativelib_AwesomeLib
    Method:    helloHabr
    Signature: ()V
*/
JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_helloHabr(
  JNIEnv *,
  jobject
);

/*
    (Наш новый метод!)
    Class:     nativelib_AwesomeLib
    Method:    getRandom
    Signature: ()I
*/
JNIEXPORT jint JNICALL Java_nativelib_AwesomeLib_getRandom(
  JNIEnv *,
  jobject
);

#ifdef __cplusplus
}
#endif
#endif
  1. Now let’s write a C++ code that will return a random number. We will connect the necessary library for this #include <ctime>, then we implement the simplest solution for obtaining a number in C++:
#include "nativelib_AwesomeLib.h"
#include <iostream>
#include <ctime> // new lib

JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_helloHabr(
  JNIEnv* env,
  jobject thisObject
  ) {
      std::cout << "Hello Habr! This is C++ code!!" << std::endl;
  }

// Новый метод
JNIEXPORT jint JNICALL Java_nativelib_AwesomeLib_getRandom(
  JNIEnv* env,
  jobject obj
  ) {
      std::srand(std::time(nullptr));
      int randomValue = std::rand();
      return randomValue;
  }

As can be seen from the example (JNIEXPORT jint), we do not return int, and jintalthough everything will work fine in this example, but in some cases you will have to cast to the JNI format. For example, so: return (jint) variable;.

How to pass a List

How do I pass a simple type to the constructor? There are no problems. And what if you need to convey something more complicated? For example, List<List<Float>>?

  1. Creating another method in our cool library:
public class AwesomeLib {
    // code 

    public native void printMatrix(float[][] matrix);
}
  1. But in the title (nativelib_AwesomeLib.h) will be jobjectArray – not at all what we would like to see. But you have to live with it.
/*
 * Class:     nativelib_AwesomeLib
 * Method:    printMatrix
 * Signature: ([[F)V
 */
JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_printMatrix(
    JNIEnv *,
    jobject,
    jobjectArray
);
  1. Пишем реализацию на С++:

JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_printMatrix(
    JNIEnv * env,
    jobject obj,
    jobjectArray matrix
) {
    std::cout << "C++ code: print jobjectArray: " << matrix << std::endl;
}
  1. Передали, попробуем запустить программу (не забывайте, что после каждого изменения .cpp необходимо запустить в консоли команды для обновления .dll, это описано в первой статье):

Видим только адрес, а если будет matrix[0] or matrix[0][0] ? In fact, neither one nor the other simply compiles, although it will work fine for jintArray. It’s all about jobjectArray: first you need to get the necessary types from the array, skast and only then print. So we came to the next topic.

Loop through the array in native

To walk through the cycle, you need:

  1. Find out the length of the array GetArrayLength.
  2. Get an element of an array of objects GetObjectArrayElement.
  3. Skast received jobjectArray in the necessary to us jfloatArray.
  4. Write a typical C++ code.
JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_printMatrix(
    JNIEnv * env,
    jobject obj,
    jobjectArray matrix
) {
    std::cout << "C++ code: print jobjectArray:" << std::endl;
    int sizeFirstArr = env->GetArrayLength(matrix);
    
    for (int i = 0; i < sizeFirstArr; i++) {
        jfloatArray secondArr = (jfloatArray) env->GetObjectArrayElement(matrix, i);
        jfloat *elements = env->GetFloatArrayElements(secondArr, 0);
        int sizeSecondArr = env->GetArrayLength(secondArr);
        
        for (int k = 0; k < sizeSecondArr; k++) {
            float value = elements[k];
            std::cout << value << ", ";
        }
        
        std::cout << std::endl;
    }
}

Let’s run our code and see what happened:

Cool, printed. But you know what else? It’s C++, there’s no gc and magic for you here, be kind, clean up after yourself.

env->ReleaseFloatArrayElements(secondArr, elements, 0);
env->DeleteLocalRef(secondArr);

You need to release the memory yourself. Let this code be called from Java, but there is no garbage collector, and leaks are your headache. In fairness, I think that another team that knows it well should deal with complex C++ code, but if this is your pet project and you are alone, then do not forget about the features.

How to call a Java method from C++?

We have learned simple but basic operations. How to call the method, how to return the value and pass it to the constructor. But sometimes I want to pull something useful from Java from C++. How to pull off such a trick?

  1. In our class AwesomeLib.java let’s create a regular method that we will call from C++. To make it more interesting, we will pass float and int to it. Call from Main our method from the first article — helloHabr. Let’s modify it a little and call the Java method from it. This way we will have a sequence of calls: Java → C++ → Java.
public class AwesomeLib {
    // from article: JNI Part 1
    public native void helloHabr();

    // new code
    public void printNativeResult(float value1, int value2) {
        System.out.println(
          "Java code: value1: " + value1 + " value2: " + value2
        );
    }
}
  1. Go to AwesomeLib.cpp and we change the method from the first article:
JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_helloHabr(
    JNIEnv* env,
    jobject thisObject
) {
    std::cout << "Hello Habr! This is C++ code!!" << std::endl;

    jclass cls_awesome_lib = env -> GetObjectClass(thisObject);
    jmethodID mid_compare = env->GetMethodID(
        cls_awesome_lib,
        "printNativeResult",
        "(FI)V"
    );
    
    // call method
    env->CallVoidMethod(
        thisObject,
        mid_compare,
        2.0,
        3
    );
}

What’s going on here?

  • First, we find and get a Java object, so that we can call its methods later.
  • Get the id of the method to access it. We specify the name of the method and what in our case it contains in the constructor “(FI)”, since we have float and int in it. V indicates that this is a void method, not a return method.
  • Calling the method.
  1. Updating .dll and run our code:

Hooray, we can run Java code from native code!

A little more about GetMethodID:

  • “**(FI)V**” → void someFunc(float, int)
  • “**(FF)V**” → void someFunc(float, float)
  • “**(FI)Z**” → boolean someFunc(float, int)
  • “**()Z**” → boolean someFunc()

We refer to the information from the Oracle website:

Conclusion

Finally, I will say that JNI is not quite ordinary C/C++. But don’t be afraid, you need to explore! In my short series about JNI, there is the last article left, in it I will tell you about the most important and interesting thing: performance! I’ll write simple synthetic tests and show you when JNI makes sense and when it doesn’t.

Unity 3D Development Outsourcing | IT Outsource Support

Ready to see us in action:

More To Explore

IWanta.tech
Logo
Enable registration in settings - general
Have any project in mind?

Contact us:

small_c_popup.png