Blending in Native Code (C/C++)

Problem

You wish to call native C/C++ functions from Java, either for efficiency or to access hardware- or system-specific features.

Solution

Use JNI, the Java Native Interface.

Discussion

Java lets you load native or compiled code into your Java program. Why would you want to do such a thing? One reason might be to access OS-dependent functionality. Another is speed: native code will likely run faster than Java, at least at present. Like everything else in Java, this mechanism is subject to security restrictions; for example, applets are not allowed to access native code.

The native code language bindings are defined for code that has been written in the C or C++ language. If you need to access a language other than C/C++, write a bit of C/C++ and have it pass control to other functions or applications, using any mechanism defined by your operating system.

Due to such system-dependent features as the interpretation of header files and the allocation of the processor’s general-purpose registers, your native code may need to be compiled by the same C compiler used to compile the Java runtime for your platform. For example, on Solaris you can use SunPro C, or maybe gcc. On Win32 platforms, use Microsoft Visual C++ Version 4.x or higher (32 bit). For other platforms, see your Java vendor’s documentation.

Also note that the details in this section are for Java 1.1’s Java Native Interface (JNI) that differs in some details from 1.0 and from Microsoft’s native interface.

The steps to call native code are summarized in the following sidebar and detailed below.

The first step is to write Java code that calls a native method. To do this, use the keyword native to indicate that the method is native, and provide a static code block that loads your native method using System.loadLibrary( ) . (The dynamically loadable module is created in Step 5.) Static blocks are executed when the class containing them is loaded; loading the native code here ensures it is in memory when needed!

Object variables that your native code may modify should carry the volatile modifier. The file HelloWorld.java , shown in Example 26-7, is a good starting point.

Example 26-7. HelloWorld.java

/**
 * A trivial class to show Java Native Interface 1.1 usage from Java.
 */
public class HelloWorld {
    int myNumber = 42;        // used to show argument passing

    // declare native class
    public native void displayHelloWorld(  );

    // Application main, call its display method
    public static void main(String[] args) {
        System.out.println("HelloWorld starting; args.length="+
            args.length+"...");
        for (int i=0; i<args.length; i++)
            System.out.println("args["+i+"]="+args[i]);
        HelloWorld hw = new HelloWorld(  );
        hw.displayHelloWorld(  );        // call the native function
        System.out.println("Back in Java, "myNumber" now " + hw.myNumber);
    }

    // Static code blocks are executed once, when class file is loaded
    static {
        System.load("libhello.so");
    }
}

The second step is simple; just use javac HelloWorld as you normally would. You probably won’t get any compilation errors on a simple program like this; if you do, correct them and try the compilation again.

Next, you need to create an .h file. Use javah to produce files:

javah -jni HelloWorld           // produces HelloWorld.h

The -jni is required; without it, you get incompatible 1.0 output.

The .h file produced is a “glue” file, not really meant for human consumption and particularly not for editing. But by inspecting the resulting .h file, you’ll see that the C method’s name is composed of the name Java, the package name (if any), the class name, and the method name:

void Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject this);

Then, create a C function that does the work. You must use the same function signature as is used in the .h file:

void Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject this) {

This function can do whatever it wishes. Note that it is passed two arguments: a JVM environment, and a handle for the this object. Table 26-2 shows the correspondence between Java types and the C types (JNI types) used in the C code.

Table 26-2. Java and JNI types

Java type

JNI

Java array type

JNI

byte
jbyte
byte[]
jbyteArray
short
jshort
short[]
jshortArray
int
jint
int[]
jintArray
long
jlong
long[]
jlongArray
float
jfloat
float[]
jfloatArray
double
jdouble
double[]
jdoubleArray
char
jchar
char[]
jcharArray
boolean
jboolean
boolean[]
jbooleanArray
void
jvoid
  
Object
jobject
Object[]
jobjectArray
Class
jclass
String
jstring
array
jarray
Throwable
jthrowable

Example 26-8 is a complete C native implementation. Passed an object of type HelloWorld, it increments the integer myNumber contained in the object.

Example 26-8. HelloWorld.c

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

/*
 * This is the 1.1 implentation of displayHelloWorld.
 */
void Java_HelloWorld_displayHelloWorld(JNIEnv *env, jobject this)
{
    jfieldID fldid;
    jint n, nn;

    (void)printf("Hello from a Native Method
");

    if (this == NULL) {
        fprintf(stderr, "Input pointer is null!
");
        return;
    }
    if ((fldid = (*env)->GetFieldID(env, 
        (*env)->GetObjectClass(env, this), "myNumber", "I")) == NULL) {
        fprintf(stderr, "GetFieldID failed");
        return;
    }

    n = (*env)->GetIntField(env, this, fldid);    /* retrieve myNumber */
    printf(""myNumber" value is %d
", n);

    (*env)->SetIntField(env, this, fldid, ++n);    /* increment it! */
    nn = (*env)->GetIntField(env, this, fldid);

    printf(""myNumber" value now %d
", nn);         /* make sure */
    return;
}

Finally, you compile the C code into a loadable object. Naturally, the details depend on platform, compiler, etc. For example, on Windows 95:

> set JAVAHOME=C:java              # or wherever
> set INCLUDE=%JAVAHOME%include;%INCLUDE%
> set LIB=%JAVAHOME%lib;%LIB%
> cl HelloWorld.c -Fehello.dll -MD -LD javai.lib

And on Unix:

$ export JAVAHOME=/local/java   # or wherever
$ cc -I$JAVAHOME/include -I$JAVAHOME/include/solaris 
-G HelloWorld.c -o libhello.so

Example 26-9 is a makefile for Unix.

Example 26-9. Unix makefile

# Makefile for the 1.1 Java Native Methods examples for
# Learning Tree International Course 471/478.
# Has been tested on Solaris both with "gcc" and with SunSoft "cc".
# On other platforms it will certainly need some tweaking; please
# let me know how much! :-)

# Configuration Section

CSRCS        = HelloWorld.c
JAVAHOME    = /local/jdk1.1.2
INCLUDES    = -I$(JAVAHOME)/include -I$(JAVAHOME)/include/solaris 
LIBDIR        = $(JAVAHOME)/lib/sparc/green_threads
CLASSPATH    = $(JAVAHOME)/lib/classes.zip:.

all:        testhello testjavafromc

# This part of the Makefile is for C called from Java, in HelloWorld
testhello:        hello.all
        @echo
        @echo "Here we test the Java code "HelloWorld" that calls C code."
        @echo
        LD_LIBRARY_PATH=`pwd`:. java HelloWorld

hello.all:        HelloWorld.class libhello.so

HelloWorld.class: HelloWorld.java
        javac HelloWorld.java

HelloWorld.h:    HelloWorld.class
        javah -jni HelloWorld

HelloWorld.o::    HelloWorld.h

libhello.so:    $(CSRCS) HelloWorld.h
    $(CC) $(INCLUDES) -G $(CSRCS) -o libhello.so

# This part of the Makefile is for Java called from C, in javafromc
testjavafromc:    javafromc.all hello.all
    @echo
    @echo "Now we test HelloWorld using javafromc instead of java"
    @echo
    LD_LIBRARY_PATH="$(LIBDIR):." CLASSPATH="$(CLASSPATH)" ./javafromc HelloWorld
    @echo
    @echo "That was, in case you didn't notice, C->Java->C. And,"
    @echo "incidentally, a replacement for JDK program "java" itself!"
    @echo


javafromc.all:    javafromc

javafromc:    javafromc.o
    $(CC) -L$(LIBDIR) javafromc.o -ljava -o $@

javafromc.o:    javafromc.c
    $(CC) -c $(INCLUDES) javafromc.c

clean:
    rm -f core *.class *.o *.so HelloWorld.h
clobber: clean
    rm -f javafromc

And you’re done! Just run the Java interpreter on the class file containing the main program. Assuming that you’ve set whatever system-dependent settings are necessary (possibly including both CLASSPATH and LD_LIBRARY_PATH or its equivalent), the program should run as follows:

C> java HelloWorld
Hello from a Native Method        // from C
"myNumber" value is 42            // from C
"myNumber" value now 43        // from C
Value of myNumber now 43        // from Java

Congratulations! You’ve called a native method. However, you’ve given up portability; the Java class file now requires you to build a loadable object for each operating system and hardware platform. Multiply {MS-Windows 95/98, MS-Windows CE, Me, and NT, MacOS, Sun Solaris, HP/UX, Linux, OpenBSD, NetBSD, FreeBSD} times {Intel, SPARC, PowerPC, HP-PA, Sun3} and you begin to see the portability issues. Your native code can be used in server code and desktop applications, but is normally not permitted in web browsers.

Beware that problems with your native code can and will crash the runtime process right out from underneath the Java Virtual Machine. The JVM can do nothing to protect itself from poorly written C/C++ code. Memory must be managed by the programmer; there is no automatic garbage collection of memory obtained by the system runtime allocator. You’re dealing directly with the operating system and sometimes even the hardware, so, “Be careful. Be very careful.”

See Also

If you need more information on Java Native Methods, you might be interested in the comprehensive treatment found in Essential JNI: Java Native Interface by Rob Gordon (Prentice Hall).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.152.198