[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]
COMP2100/2500
Lecture 26: The Java/C InterfaceSummary
How to incorporate C code into Java projects.
Outline
Introduction.
Some nuts and bolts of how to do it.
Examples.
Introduction
In the real world, we can't restrict ourselves to using nothing but Java (or whatever your favourite language is), no matter how much we'd like to.
We may need to use packages or libraries written in languages other than Java.
Some part of the system may have been written in some language other than Java.
Some parts of the system may need to have direct access to low-level system details.
One option is just to forget Java and write the system in another language, and this is often the right answer. Language choice should not just be a matter of personal preference, but should be carefully thought out for each project. By the time you finish your degree you should be familiar with several languages and capable of learning a new one in a few weeks.
But what if no language has all the libraries, capabilities and features that we need? We need to be able to make software components written in different languages work together. In practice, large systems often incorporate components written in several different languages.
Today we'll just look at one example of multi-language systems, making Java and C work together. In fact we'll only look at one direction: calling C code from Java.
JNI: The Java Native Interface
The Java Language Specification states that a method may be labelled as native (as you might label other methods abstract), which means that it is "implemented in platform-dependent code, typically written in another programming language such as C, C++, Fortran, or assembly language". But the JLS doesn't specify how to write your e.g. C code and how to connect the two pieces together.
The Java Native Interface (JNI) is Sun's way of connecting the implementations of native methods to its JVM. It specifies what you have to do in your C code to conform to the requirements of the JVM, and what you have to do in your Java code to tell the JVM to access the external code.
Understand that once we start going down the path to using native code we lose platform-independence. Our examples work with Sun's JDK on Linux. With any other combination of platform and JVM changes may (will?) be needed to both the C code and the Java code.
A simple example
Suppose you wanted to access the system clock so as to measure how long your program takes to perform different operations. This is easy to do in C, using a library routine clock() and a macro CLOCKS_PER_SEC, both declared in the standard header file time.h.
Here's the Java code:
public class Timer { private native int ticks(); private native int ticksPerSecond(); static { System.loadLibrary("timer"); } }And here's the C code it calls.
#include <jni.h> #include <time.h> #include "Timer.h" /* The external C part of a Java system stopwatch package */ JNIEXPORT jint JNICALL Java_Timer_ticks (JNIEnv *env, jobject obj) { return (jint) clock(); } JNIEXPORT jint JNICALL Java_Timer_ticksPerSecond (JNIEnv *env, jobject obj) { return (jint) CLOCKS_PER_SEC; }Java routines such as ticks and ticksPerSecond are known as wrappers for the corresponding C routines.
Notice the phenomenon of name mangling -- names in the Java class (e.g. ticks()) have to be matched up with names in the C code (Java_Timer_ticks()). The JNI specification defines how this relationship works.
Because of the mangling, there's no shortcut to accessing C library or system calls. At least, we can stick private on the declaration.
What are the two parameters to the C functions? The first is a means of accessing the JVM if you need it. The second is a reference to this (the object in which the method was invoked). In this example we don't use either of the parameters.
In the Java class Timer (which you will use in this week's lab) there are also attributes for storing the number of 'ticks' when the clock was started and stopped, queries for returning the number of ticks and seconds between starting and stopping it, and commands to start and stop the clock. The excerpt above is just the bit that made the external calls -- those routines aren't even visible to the client.
Issues for external calls
What if you want to pass parameters to the external routine? How do you know that the data representations are consistent?
Same for the return value of a function.
What if the external code has the only reference to an object? How can the garbage collector know not to clean it up?
Another example
Also from the lab, a routine to access an array element. First, the fragment of Java:
private native int c_item(int array,int index);then the implementation in C:
/* Return the element at index i of the array a. */ JNIEXPORT jint JNICALL Java_IntegerArray_c_1item(JNIEnv *env, jobject obj, jint a, jint i){ return (jint)(((int *)a)[i]); }Note the casts: first, a is cast to the type int *. Then, a is used as an array, and the i'th element is accessed. Finally, the result is cast back to a jint for use by the JVM.
Further thoughts
The Javadoc documentation of a class doesn't tells you if a method is native. A good thing.
- You can't (yet) add a JML contract to a native method. This is a consequence of the way JML wraps around your code. If you want to have contracts, write (another!) Java wrapper that calls the native method and add the contract to the wrapper.
Implementation with Sun's JDK on Linux
Remember the previous warning that some of the details will be different for other operating systems and JVMs.
- The JDK comes with a file jni.h which you #include in your C code.
- The header file defines special types jint, jdouble, etc. for you to use to refer to values of Java basic types. Such values can be parameters to and return values from your C functions.
- There are separate releases of Sun's JDK for each OS and hardware platform. So Sun does the work of writing a jni.h file that has the right values.
- There are also types jstring, jintArray, jlongArray, etc. All other Java reference types have to be referred to as jobject.
- When used in C code, the value of a jint is the value of the corresponding Java int.
- How did I know what the definition of the C function should look like? You compile Timer.java to get Timer.class, then run javah -jni Timer to get Timer.h, which should be #included by timer.c. Timer.h looks like this:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class Timer */ #ifndef _Included_Timer #define _Included_Timer #ifdef __cplusplus extern "C" { #endif /* * Class: Timer * Method: ticks * Signature: ()I */ JNIEXPORT jint JNICALL Java_Timer_ticks (JNIEnv *, jobject); /* * Class: Timer * Method: ticksPerSecond * Signature: ()I */ JNIEXPORT jint JNICALL Java_Timer_ticksPerSecond (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif- How do you work with Strings? JNI provides C functions:
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len); jsize GetStringLength(JNIEnv *env, jstring string); const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy); void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);etc. Similar sorts of methods for dealing with arrays.
How to compile systems with external calls
To compile the system above you need to say
javac Timer.java javah -jni Timer gcc -Wall -shared -I/usr/local/java/include \ -I/usr/local/java/include/linux timer.c -o libtimer.soYou'll be using this library in the lab. For the JVM to pick up libtimer.so you have to tell it where to look. This can be done including the current directory in the environment variable LD_LIBRARY_PATH. You can set variables on a per-command basis using the env command, e.g.:
env LD_LIBRARY_PATH=. java ArrayTesterwhich is how it's done in the supplied Makefile.
Calling Java from C
So far we have limited ourselves to calling C routines from Java. What if we want the C part of a system to be able to call Java routines?
Why would we want to do this? One reason is if we were trying to write an Java interface to something like GTK+ (a graphics library), where the main loop is running on the C side, and it needs to call Java routines when various events take place.
How do we do it? Not at all in this course. The details are given in Sun's JNI documentation.
[ANU] [DCS] [COMP2100/2500] [Description] [Schedule] [Lectures] [Labs] [Homework] [Assignments] [COMP2500] [Assessment] [PSP] [Java] [Reading] [Help]
Copyright © 2005, Richard Walker, The Australian National University
Version 2005.4, Sunday, 22 May 2005, 18:04:59 +1000
Feedback & Queries to
comp2100@cs.anu.edu.au