Controlling I/Os on the Revolution Pi with Java

After laying the foundations for the use of Java on the RevPi and JNI in the previous article, I am now porting piControlIf to Java. In the beginning, I had to clarify the question of whether I should write a wrapper for the Linux system functions or one for piControlIf. Writing the wrapper for piControlIf seemed more manageable to me.

In retrospect, it was quite a lot of hard work, since accessing the arrays of objects via JNI is quite cumbersome. Probably a different approach would have been better. But afterwards one is always wiser. Finally, I would like to show in this article how PiControl can be used in Java. Everyone can implement this in the way that suits them best.

Porting the API to Java

First of all, we need to create a new project, which I call PiControlIfJava. Create a class with the following methods:

private native void open();
private native void close();
public native int reset();
public native int read(long Offset, long Length, short pData);
public native int write(long Offset, long Length, short pData);
public native int getDeviceInfoList(SDeviceInfo pDev);
public native int getBitValue(SPIValue pSpiValue);
public native int setBitValue(SPIValue pSpiValue);
public native int getVariableInfo(SPIVariable pSpiVariable);
public native int findVariable(String name);

In addition, a lot of constants from piControl.h are needed, such as PICONTROL_LED_A1_GREEN. I simply copied all the constants into the Java file and did not check which of them are actually needed.

As far as possible, I took over the structure of piControlIf 1:1 from the C++ implementation. I named classes, methods, etc. the same and merely adapted them to the Java style, e.g. methods always begin with a lowercase letter. I put the classes in a package with the name com.kunbus.revpi.

I also removed the Hungarian notation, which already has no place in C code and even more so in Java. Linux Torvalds put it in his typically blunt way as follows:  “Encoding the type of a function into the name (so-called Hungarian notation) is brain damaged […]”. Even Microsoft, probably the main cause of the spread of this bad habit, now advises against it. If you need something to laugh about, you’ve come to the right place.

Converting the variable types

Since the variable types in C differ in some corners from those in Java, e.g. there are no unsigned types, I used the following conversions:

C

Java

Unsigned byte

int

Unsigned short

int

Unsigned int

long

Char *

String

After the porting work is done, you have to include the reference to the external library. I named the library that I am about to create with the JNI interface PiControlIfWrapper.

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

Creating the JNI interface

First of all, I have to export the interface to C-Files. This is done via

<path>\javac -h . PiControl.java SPIValue.java SPIVariable.java SDeviceInfo.java

The Java compiler now creates a .h-file with entries of the following type:

JNIEXPORT jint JNICALL Java_com_kunbus_revpi_PiControl_findVariable (JNIEnv *, jobject, jstring);

I create a project PiControlWrapper, as mentioned above. Now I insert the generated header file and create a cpp file with the same name in the project. I configure the project so that a dynamic library is generated by the compiler.

Don’t forget to include the libpiControlIf.so  and remember to specify it in Netbeans for all configurations (debug/release). Otherwise, you will only notice the error at the very end when you run the Java test program. Or, like me, you’ll spend an hour looking for the cause.

Accessing variables with JNI

It is noticeable that the method name is extremely bulky, as Java uses the so-called fully qualified name, i.e. including the package. Two parameters are always given to each method. A JNIEnv* and a jobject. The jobject is simply the Java object from which the call was made, i.e. a kind of this reference. JNIEnv* allows access to all data that would also be possible from the Java object.

Now it’s getting a little tricky here. As great as Java is, as complicated is JNI. You have to get the impression that JNI is intentionally kept complicated to discourage users from working with it. Explaining all the code is beyond the scope of this blog. Therefore, I will only show a few extracts.

Primitives such as int are simply passed as jint and treated directly in C like a normal integer. With strings, it’s getting a bit more complicated. If I want to access the string in the parameters in the findVariable method, it looks like this:

const char *varNameCStr = env->GetStringUTFChars(name, NULL);

 if (varNameCStr == NULL)
        return;

//do something with varNameCStr

 env->ReleaseStringUTFChars(name, varNameCStr);

If fields of a class have to be read out, the tipping effort becomes even more:

class spiValueClass = env->GetObjectClass(spiValue); //get the class identifier

jfieldID addressFieldID = env->GetFieldID(spiValueClass, "address", "I"); // get the field identifier inside the class

if (addressFieldID == NULL) {
    return;
}

jint = env->GetIntField(spiValue, addressFieldID); //  geht the actual value of the field

“I” is the so-called field signature, in this case, it is an integer value, for a double it would be a “D”, for example. Here you will find a complete table with the field signatures.

Accessing fields with JNI

I don’t want to go into the finer points of JNI. There is enough documentation on the net. However, I would like to point out a pitfall that I have picked up. If a field in an object is another object, e.g. a string, I need the fully-qualified class for the field signature. Instead of a simple “I” for integer, it then becomes L<fully-qualified-class>;. It could hardly have been made more complicated.

If I want to access a string within a field, it looks like this:

jfieldID varNameFieldID = env->GetFieldID(spiVariableClass, "varName", "Ljava/lang/String;");

That’s enough about JNI and on to the test program. After you finished to implementation of the wrapper, you created the dynamic library. Finally, you have to copy the .so file into the directory of the Java project PiControlIfJava so that it can be found by Java.

A test program for the javanese piControl

For this, I used an old friend: RevPiBlink. I would now like to implement this in Java. After I created a new Project called RevPiBlinkJava, I just copied the code from RevPiBlink and adapted it to Java. Fortunately, Java is not far away from C++. So this looks quite familiar:

public class PiControlIfJavaTest {
    public static int getRevPiLEDAddress(PiControl piC) {
        SPIVariable spiVariableOut = new SPIVariable("RevPiLED", 0, 0, 0);
        if (piC.getVariableInfo(spiVariableOut) != 0) {
            return -1;
        } else {
            return (spiVariableOut.address);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        PiControl piC = new PiControl();

        SPIValue spiValue = new SPIValue(0, 0, 0);
        spiValue.address = getRevPiLEDAddress(piC);
        spiValue.bit = 0;
        spiValue.value = 1;

        while (true) {
            spiValue.value = spiValue.value ^ 0x01;
            piC.setBitValue(spiValue);
            sleep(1000);
        }
    }
}

Now you only have to use the already created Java library PiControlJava. In Netbeans right click on the Libraries folder in your project and Add JAR/Folder and then add PiControlIfJava.jar.

Screenshot of Netbeans showing the context menu of the libraries folder

Now you can create and run the project.

The garbage collector and its influence on performance

If you need a certain temporal reliability, you make sure that the garbage collector does not start if possible. This leads the Java principle somewhat ad-absurdum because the garbage collector is actually a sensible thing. It ensures that no memory leaks occur due to lost references. Unfortunately, the Java inventors have fully relied on the GC and Java does not support the possibility of explicitly destroying objects, as one is used to from C++ with delete. Objects are simply dereferenced and at some point, the garbage collector comes along and releases the memory.

In my student days (a few years ago) we did measurements with the garbage collector. We had a program that created many objects and released them again. It was easy to see that after a while the garbage collector started up and from then on it was almost permanently working in the background. From this point on, the performance was significantly lower than at the start of the program.

A way around the garbage collector

Tests with my project at university showed that the program became significantly faster when objects that were no longer needed were not simply released into nirvana (dissolve reference). Instead, I did object recycling. A class accepted objects that were no longer needed and managed them in a list. Instead of simply creating new objects, it was first queried whether an orphaned object of the class already existed. During the runtime of the program, there was no longer any drop in performance. The overall performance also suffered almost not at all.

As mentioned, the tests were carried out several years ago and I cannot say how far the statements are still valid. If the garbage collector is a real problem for the project, you have to pay attention to the handling of objects. Specifically, avoid classes that implicitly create new objects, e.g. strings.