Recitation R5.1: Java and C


No submission.


About The Recitation

This recitation will show how you can call C code from Java and Java code from C. This can be especially helpful when writing Android applications, as applications are generally written in Java but low level Android libraries for networking and graphics, etc., are written in C/C++. There are lots of resources on the internet to help you with this, but here is one I found particularly useful. Also in todays recitation, if you have time, you can explore applying object oriented principals to C.

JNI

Java Native Interface (JNI) is a way for Java to interface with native code, in our case C. For this to work a Java method that is implemented in C/native code needs to be labeled with the keyword native. Next the Java code needs to be compiled into Java byte code and a C header file produced that defines the interface of the C functions that will be called from Java. In the past you have used Eclipse to compile and run Java programs, but this can also be done with the command line. The commands javac [options] compiles a .java file and java [options] runs a Java program. Note, do not include the .class when running your program. Create a directory called R5.1 and copy the following code into a file called JNIdemo.java

public class JNIDemo{
    static { 
        System.loadLibrary("sum");
    }

    private static native int sum(int[] x);

    public static void main(String[] args){
        int x[] = {1, 2, 3, 4};

        System.out.println("Java: sum() returned: " + sum(x));
    }
}

Lets take a look at whats happening in this code. First the System.loadLibrary("sum"); tells java to load a library called sum. The actual name of the library in linux will be libsum.so . The .so means the library is a shared library, you can read more about shared libraries here: here. We will actually create this library in C. To compile this java file and create the .h C file we will need, we can use the command:


        javac -h . JNIDemo.java 
    

The -h . tells the Java compiler to generate a .h file and place it in the current directory. Now you should have a file called JNIDemo.h . Use your favorite text editor to open the file an get a feel for how the Java functions that you declared as nativehave been translated into C function declarations.

Back to C

Now it is time to create the .c file that will implement the functions declared in the .h file. Copy the following code into a file called sum.c:


        #include <jni.h>
        #include <stdio.h>
        #include "JNIDemo.h"
        
        
        JNIEXPORT jint JNICALL Java_JNIDemo_sum(JNIEnv * env, jclass j_class, jintArray j_arr){
            jint *arr = (*env)->GetIntArrayElements(env, j_arr, 0);
            jint size = (*env)->GetArrayLength(env, j_arr);
        
            printf("C: in sum method\n");
            jint sum = 0;
            for (int i = 0; i < size; i++){
                sum += arr[i];
            }
            return sum;
        }

There is a lot going on here so lets see if we can break down what is happening. First we are including the file jni.h this header file is included with the JDK and contains information about functions and types that are needed to access Java based data in C. Also we will need to tell the C compiler were to find this .h file since it is not part of the standard C library and not located in the current directory. JNIEXPORT and JNICALL are macros that ensure the function is visible to code using the shared library that the .c file will be compiled into. The function returns a jint which is a typedef that ensures the integer type in the C code is the same size as a Java int. Take a few minutes to look through this tutorial and the Oracle docs here to try and understand the rest of the code in sum.c .

Compile and Run

Now we can compile and run the code. Use the following command to generate the .so file from sum.c . The -I/usr/... -I/usr... tells GCC were to find jni.h and associated library files that contain the JNI functions used in sum.c


        gcc -fPIC -I/usr/lib/jvm/java/include -I/usr/lib/jvm/java/include/linux -shared -o libsum.so sum.c
    
Now to run the code we just need to tell Java where to find the library with the native code. In our case the library file we just generated is in the current directory so we can just use the .

        java -Djava.library.path=. JNIDemo
    

Call Java method from C

So far we have called a function in C from Java. Now lets call a Java function from C. First we need to give our C code a way to reference our Java class and environment. This can be done in a similar way to the Java code that called the C sum function above. In this case we can make a Java native method called link. Then in our C code the generated link will be passed a pointer to a struct that represents the java environment. Now we can use a JNI library function to access other methods in the Java code, in this case the print function. Once we have the Java method ID we can use it to call the corresponding Java method from C. Now JNIdemo.java should look like this:


            public class JNIDemo{

                static { 
                    System.loadLibrary("sum");
                }
            
                private static native int sum(int[] x);
            
                private static native void link();
            
                private static void print(String s){
                    System.out.println("Java: received message: " + s);
                }
            
                public static void main(String[] args){
                    int x[] = {1, 2, 3, 4};
            
                    System.out.println("Java: sum() returned: " + sum(x));
            
                    link();
                }
            }
    
And sum.c should look like this:

        #include <jni.h>
        #include <stdio.h>
        #include "JNIDemo.h"
        
        JNIEXPORT jint JNICALL Java_JNIDemo_sum(JNIEnv * env, jclass j_class, jintArray j_arr){
            jint *arr = (*env)->GetIntArrayElements(env, j_arr, 0);
            jint size = (*env)->GetArrayLength(env, j_arr);
        
            printf("C: in sum method\n");
            jint sum = 0;
            for (int i = 0; i < size; i++){
                sum += arr[i];
            }
            return sum;
        }
        
        JNIEXPORT void JNICALL Java_JNIDemo_link(JNIEnv * env, jobject class){
            printf("C: accessing callback method\n");
            jmethodID j_print = (*env)->GetStaticMethodID(env, class, "print", "(Ljava/lang/String;)V");
        
            jstring j_string = (*env)->NewStringUTF(env, "Hello from C");
        
            (*env)->CallStaticVoidMethod(env, class, j_print, j_string);
        }
    

To get the code to run you will need to repeat the earlier steps that compile the Java code and generate the .h file, compile sum.c into libsum.so, and finally run the Java code with the location of libsum.so specified.

If everything is working correctly your output should look like this:


        C: in sum method
        Java: sum() returned: 10
        C: accessing callback method
        Java: received message: Hello from C        

Next Steps

Now its your turn, use the links above and/or google to figure out how to add a Java native method that takes a String argument and returns the integer value of the String. Next try and change the value of a variable, either static or instance, from C using a similar technique to the link method that our C code used to call the Java print method.