Accessing system functions in Java via JNI

In the last article I took the first steps with Java on the RevPi. Currently, however, we do not yet have access to the extension modules. For this we need access to system functions. Since Java is designed to be fundamentally platform-independent, Java (to my knowledge) does not provide any direct possibility to access Linux system functions. Java does, however, offer an interface for system-related programming, the so-called Java Native Interface or JNI.

Originally, I would have liked to use a ready-made library for accessing the Linux system functions. Interestingly, the web did not offer much in this regard. I would have guessed that there are countless use cases for this. Very likely I lack some knowledge in this context and was simply looking for the wrong thing.

A test program

Now comes the interesting part. But before I implement piControlIf right away, I’ll start with a test program. It’s been years since my last attempts with JNI.

package jnitest;

public class JniTest {
    public native void helloWorld();

    public static void main(String[] args) {
        JniTest jniTest = new JniTest();   
        jniTest.helloWorld();
    }
}

Before we can execute the program, we must first include the C code. To do this, the Java compiler creates a corresponding header file with

javac -h . <files>

If necessary, the complete path must be specified instead of just javac if the path is not stored. In my case it looks like this:

"c:\Program Files\Java\jdk-15.0.1\bin\javac" -h . JniTest.java

Issues with the tool

Now things are going to get messy again, because another gap appears, caused by the not yet finished transfer of Netbeans to Apache. I installed a new Netbeans. Netbeans comes with rudiemntary C-support by default. The old C/C++ plug-in is no longer available for the current Netbeans version. Actually, however, old plug-ins should still be able to be installed. For some reason, however, the old plugin would not install.

After a long search, it looks as the fault was not so much with Netbeans as with Java. So back to the old Java version. To do this, simply change the relevant lines in C:\Program Files\NetBeans-12.1\netbeans\etc\netbeans.conf. It is best you just copy the line and comment out the original line:

#netbeans_jdkhome="C:\Program Files\Java\jdk-15.0.1"
netbeans_jdkhome="C:\Program Files\Java\jdk-14.0.1"

And lo and behold, the plugin installs without complaining. Exit Netbeans and write again the current version into netbeans.conf.

netbeans_jdkhome="C:\Program Files\Java\jdk-15.0.1"
#netbeans_jdkhome="C:\Program Files\Java\jdk-14.0.1"

The matching dynamic library in C++

Create a new C++ project JniTestJava for a dynamic library. Create jnitest_JniTest.cpp and move the created header file from the Java project into the C++ project. Simply move the file to the appropriate project directory.

#include<jni.h>
#include<stdio.h>

#include jnitest_JniTest.h

JNIEXPORT void JNICALL Java_jnitest_JniTest_helloWorld(JNIEnv *, jobject) {
    printf("Hello JNI\n");
}

When you try to create an empty library, you will see the files jni.h and jni_md.h  are missing. With the following lines on the RevPi you will find the missing files.

/usr/find | grep jni.h
/usr/find | grep jni_md.h

Lo and behold, the files exist, only the symlink is missing. So.

sudo -s ln /usr/lib/jvm/jdk-8-oracle-arm32-vfp-hflt/include/jni.h jni.h
sudo -s ln /usr/lib/jvm/jdk-8-oracle-arm32-vfp-hflt/include/linux/jni_md.h jni_md.h

Now you can create the library. The file libJniTestJava.so will be generated. I don’t know if there is a nomenclature for wrapper libraries in the Java world. I thought it appropriate to add Java at the end to make it clear that the .so file is intended for Java (hence the name). Now copy the .so file into the appropriate directory.

When I looked at the RevPi, I was a little surprised why the program did not end up in the directory I was used to from C. As a standard, with Java, Netbeans simply puts the file in /home/pi/NetBeansProjects and not in the long path as with the C/C++ compiler. Makes things easier. However, the directory can be adjusted in the settings of the Java platform.

Provided you are in the directory where the .so file is located, copy the library to the java project folder.

sudo cp libJniTestJava.so /home/pi/NetBeansProjects/JniTest.

Back to the Java program

After I created the .so file, I still have to tell the Java program what the file is called. To do this, I add the following lines to the class JniTest:

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

We also have to tell Java-VM where to find the .so file. We do this with the argument -Djava.library.path=<path>.  Since we put the file directly into the project directory, <path> is simply “.”. We add that under PropertiesRun VM Options.

I was a bit puzzled by the compiler warning:

warning: [options] bootstrap class path not set in conjunction with -source 8
1 warning

After a short investigation, however, the all-clear was given. The warning only means that although Java8 is compiled, newer libraries are used and the project may not run in a Java8 environment. A closer look reveals that, in contrast to C/C++, the compilation process takes place on the work computer. Only the compiled program is copied to the RevPi and executed there. Of course, Java13 is running on the workstation and not Java8.

The procedure is actually consistent, since the Java code is portable. Let’s see if this will causes trouble in the future with the libraries at some point. According to research, a –release 8 is sufficient to ensure that the compiled program really runs in a Java8 environment. For lack of a use case, I could not try it out yet. If necessary, however, you can always fall back on compiling the program on the RevPi.

Leave a Reply

Your email address will not be published. Required fields are marked *