C Bindings – Java

This is the second post in this serie. In this post I’m going to show how to create a C library for usage in Java.

Why would we want to do such a thing? Java is a platform-independent language, and C is platform dependent, so when we use a C library in Java, we’re binding ourselves to the platform this library is built for.
But when our project is intended to run on a dedicated platform, there is no such problem, and if this platform has some special hardware, or if we want to enhance our SW performance, we may have to use C. And if this platform runs Android OS, this becomes very useful, as we can develope the low-level stuff in C, and bind it to the application which is always written in Java.

We will make use of the “Java Native Interface” (JNI) framework in order to build our library. “Native” here stands for “platform-specific”. There’s an excellent Wikipedia page on JNI, I recommend reading it.

The code for this post can be found on github.

The header file

First thing we need to do is to include “jni.h”, this file contains all the definition we need for developing a Java module in C. Let’s look at the declaration of the function in our header file, and see some of these definitions:

JNIEXPORT jint JNICALL Java_LinuxSocket_open(JNIEnv *env, jobject obj,
                                             jstring jSocketName);
JNIEXPORT void JNICALL Java_LinuxSocket_close(JNIEnv *env, jobject obj,
                                              jint jSocket);
JNIEXPORT jstring JNICALL Java_LinuxSocket_read(JNIEnv *env, jobject obj,
                                                jint jSocket);
JNIEXPORT jint JNICALL Java_LinuxSocket_write(JNIEnv *env, jobject obj,
                                              jint jSocket, jstring jBuffer);
  • First thing we notice here is “JNIEXPORT” and “JNICALL” – This is how we make a “Java method” out of our C function.
  • Second thing is the function name. The underscores replace┬áthe dot operator, so we’re actually creating “Java.LinuxSocket.open”, where “LinuxSocket” is our library and “open” is a method in this library.
  • The first argument to each one of these function is a pointer to “JNIEnv”, this is our “link” to the Java runtime – We can use this parameter for creating and deleting Java objects (such as exceptions and strings), call Java objects’ methods, etc.
    The second parameter is a “jobject”, and this is the object which this method belongs to (we are not making use of it in our code)
  • We can see that we receive and return Java defined types: “jint”, “jstring” and so on.

Implementation

Let’s look at the “open” method:

#define MESSAGE_MAX_SIZE 256

JNIEXPORT jint JNICALL Java_UnixSocket_open(JNIEnv *env, jobject obj,
                                             jstring jSocketName)
{
    char message[MESSAGE_MAX_SIZE];

    const char *socket_name = (*env)->GetStringUTFChars(env, jSocketName, NULL);
    int s = socket_open(socket_name);
    
    if (s <= 0) {         jclass Exception = (*env)->FindClass(env, "java/lang/Exception");
        snprintf(message, MESSAGE_MAX_SIZE, "Couldn't open socket %s", socket_name);
        (*env)->ThrowNew(env, Exception, message);
    }

    (*env)->ReleaseStringUTFChars(env, jSocketName, socket_name);
    return (jint)s;
}

Right on the start we can see usage of the “JNIEnv” parameter. We use it for converting a Java string into a C-Style string. Notice that when we’re finished, we need to release this string, as we do right before we return.
We use “socket_open” which we get out of our marvelous unix-socket-client static library, and then we check for errors Java-style. In Java, a fully object-oriented language, the proper way of handling errors is by throwing an exception. So again, we’re using our “JNIEnv” parameter in order to create an exception object which we’re going to throw.
Even if an exception was thrown, the function is going to continue till its end (unlike the normal behaviour inside Java itself), so we have our chance to release the string before returning. In such case (exception thrown), there will be no meaning for the return value.

The rest of the functions are┬ámore or less the same, so we’ll leave them to the reader (as they say in math books), and go ahead to see how we’re going to use this library in Java.

By building our library as a shared object, and installing it at the proper place, we’re making our library available for Java applications.

The Java part

We create a class named “UnixSocket” – Same as our library functions (java.UnixSocket.*).

public class UnixSocket

In its “static” section we load our shared library.

static {
    try {
        System.loadLibrary("unixsockets");
    } catch (UnsatisfiedLinkError e) {
        System.err.println("Error: UnixSockets JNI Library failed to load\n" + e);
        System.exit(1);
    }
 }

The library functions are declared as “native”, so that Java knows that these are implemented in the JNI layer.

native private int open(String socketName);
native private void close(int socket);
native private String read(int socket);
native private int write(int socket, String out);

Our class saves the returned file descriptor as an int (fd). Here is the implementation of the class’s constructor, which calls “open”. Although it looks simple (as it actually is), you can see that it throws an exception. This is the exception we discussed in the C implementation above.

public UnixSocket(String socketName) throws Exception {
    fd = open(socketName);
 }

Testing

As I mentioned, the way I chose to implement this, “open” is called with the class constructor – We pass the socket name to the constructor, then “open” is called. Let’s see how this works out in the test program.

UnixSocket socket;
try {
    socket = new UnixSocket(args[0]);
} catch (Exception e) {
    System.out.println(e.getMessage());
    return;
}

If “open” succeeds, then socket is created successfully, otherwise, “open” (at the very bottom layer) will throw an exception, which is caught here. This exception holds an error message, and we can print it out

Now, the output of the Java test program – The program sends a (silly) string to the listener, and print out the message it got back from the listener:

$ java UnixSocketTest /tmp/socket1
Got response: Recieved 16 Byte
Got response: Recieved 15 Byte
Got response: Recieved 9 Byte
Got response: Recieved 8 Byte

And the output of the listener:

$ ./listenerd /tmp/socket1
[LISTENER] INFO  Starting "Unix Domain Sockets" demo listener
[LISTENER] INFO  * Starting communication with a client *
[LISTENER] INFO  Incoming message: Try this string
[LISTENER] INFO  Incoming message: Maybe this one
[LISTENER] INFO  Incoming message: Have fun
[LISTENER] INFO  Incoming message: Bye bye
[LISTENER] INFO  Client socket was closed by peer

Yes! Everything works!
Till next time,
Amnon.