Java Native Interface

Overview

A Java source file is compiled to a Java class file. This is a set of instructions for the class which use instructions from the Java Virtual Machine. The class file is interpreted by the Java runtime engine (java or jre). The interpreter is written in C, although other languages could have been used.

Not everything can be written in Java. Certain operations in classes are not portable across all operating systems. Examples are

These methods are native methods, meaning that the implementation is done in C, not in Java.

The object code for native methods are stored in dynamic link libraries (.dll's in Windows, .so's in Unix). You need to be able to match up the object code to the correct method. Diagramatically it looks like

Hello World

Step 1: Java source

public class HelloWorld {
    public native void sayHello();
}// HelloWorld
Step 2: Compile

javac HelloWorld.java
      
Step 3: Generate header

      javah -jni HelloWorld
      
The result is

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
The important part is the function prototype for Java_HelloWorld_sayHello
Step 4: implement C code for Java_HelloWorld_sayHello

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

JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *env, jobject obj) {
    printf("Hello world\n");
}
      
Step 5: Compile C code to object module

gcc -c -I/usr/local/src/jdk1.3/include \
       -I/usr/local/src/jdk1.3/include/linux \
       -fPIC hello.c
      
Step 6: Build shared library

gcc -shared -W1 -o libhello.so hello.o
      
Step 7: Java test harness


public class TestHello {

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

    public static void main(String[] argv){
	new HelloWorld().sayHello();
    }
}// TestHello
Step 8: Set up shared library environment

      export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
      
Step 9: Run test program

      java TestHello
      
A Makefile suitable for this is

# Set to where your JDK home is
JHOME = /usr/local/src/jdk1.3

INCLUDE = -I $(JHOME)/include -I $(JHOME)/include/linux

# Set this to something appropriate for building shared libraries
CFLAGS = $(INCLUDE) -fPIC -g

OBJS = hello.o

CLASSES = \
	HelloWorld.class \
	TestHello.class

# GNU Make rule to build Java class files
%.class: %.java
	javac $<

# Override default rule for building .o files
%.o: %.c %.class
	javah -jni $*
	gcc -c $(CFLAGS) $<

all: $(CLASSES) libhello.so

libhello.so : $(OBJS)
	gcc -shared -W1 -o libhello.1.0 $(OBJS)
	ln -f libhello.1.0 libhello.so

clean:
	rm -f *~ core *.o lib* *.class

Other requirements of JNI

C code written using JNI must also

From this, Java methods such as


class ClassName {
  native String methodName(int n);
}
will generate a C prototype of

JNIEXPORT jstring JNICALL Java_ClassName_methodName(JNIEnv *env,
                       jobject this, jint n);

Read example

A very simplified form of the FileInputStream.read() method is


JNIEXPORT jint JNICALL Java_FileInputStream.read(JNIEnv *env,
                       jobject this) {
    char ret;
    int fd, nread;
    // get the file descriptor from a private field of the
    // object
   fd = ...;
   nread = read(fd, &ret, 1);
   if (nread == 0) { /* EOF */
       return -1;
   } else if (nread == -1) { /* error */
       if (errno == EINTR) {
           JNUThrowByName(env, "java/io/InterruptedIOException");
       } else {
           JNU_ThrowIOException(env, "Read error");
       }
   } else {
       return ret;
   }
}

What else JNI can do

The JNI has mechanisms to

Serial port control using JNI

The javacomm package provides a Java API for the serial and parallel ports. It has a classes such as SerialPort


public class SerialPort {

    public SerialPort();

    public InputStream getInputStream();
    public OutputStream getOutputStream();
    public int getBaudRate();
    public void setBaudRate(int rate);
}

An example program to read from the serial port is



/*
 * @(#)SimpleRead.java	1.12 98/06/25 SMI
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license 
 * to use, modify and redistribute this software in source and binary
 * code form, provided that i) this copyright notice and license appear
 * on all copies of the software; and ii) Licensee does not utilize the
 * software in a manner which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind.
 * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND
 * ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY
 * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE
 * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS
 * BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES,
 * HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING
 * OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control
 * of aircraft, air traffic, aircraft navigation or aircraft
 * communications; or in the design, construction, operation or
 * maintenance of any nuclear facility. Licensee represents and
 * warrants that it will not use or redistribute the Software for such
 * purposes.
 */

import java.io.*;
import java.util.*;
import javax.comm.*;

public class SimpleRead implements Runnable, SerialPortEventListener {
    static CommPortIdentifier portId;
    static Enumeration portList;

    InputStream inputStream;
    SerialPort serialPort;
    Thread readThread;

    public static void main(String[] args) {
        portList = CommPortIdentifier.getPortIdentifiers();

        while (portList.hasMoreElements()) {
            portId = (CommPortIdentifier) portList.nextElement();
            if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                // if (portId.getName().equals("COM1")) {
                if (portId.getName().equals("/dev/term/a")) {
                    SimpleRead reader = new SimpleRead();
                }
            }
        }
    }

    public SimpleRead() {
        try {
            serialPort = (SerialPort) portId.open("SimpleReadApp", 2000);
        } catch (PortInUseException e) {}
        try {
            inputStream = serialPort.getInputStream();
        } catch (IOException e) {}
	try {
            serialPort.addEventListener(this);
	} catch (TooManyListenersException e) {}
        serialPort.notifyOnDataAvailable(true);
        try {
            serialPort.setSerialPortParams(9600,
                SerialPort.DATABITS_8,
                SerialPort.STOPBITS_1,
                SerialPort.PARITY_NONE);
        } catch (UnsupportedCommOperationException e) {}
        readThread = new Thread(this);
        readThread.start();
    }

    public void run() {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {}
    }

    public void serialEvent(SerialPortEvent event) {
        switch(event.getEventType()) {
        case SerialPortEvent.BI:
        case SerialPortEvent.OE:
        case SerialPortEvent.FE:
        case SerialPortEvent.PE:
        case SerialPortEvent.CD:
        case SerialPortEvent.CTS:
        case SerialPortEvent.DSR:
        case SerialPortEvent.RI:
        case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
            break;
        case SerialPortEvent.DATA_AVAILABLE:
            byte[] readBuffer = new byte[20];

            try {
                while (inputStream.available() > 0) {
                    int numBytes = inputStream.read(readBuffer);
                }
                System.out.print(new String(readBuffer));
            } catch (IOException e) {}
            break;
        }
    }
}

The classes allow almost platform independent access to the serial ports. There is still some dependency in the port names (COM1 for Windows, /dev/term/a for Solaris, /dev/ttyS0 for Linux). But all the rest of the platform dependent code is hidden in native code libraries accessed by JNI.

Here is part of the code for the "open" operation on a serial port


/*----------------------------------------------------------
RXTXPort.open

   accept:      The device to open.  ie "/dev/ttyS0"
   perform:     open the device, set the termios struct to sane settings and
                return the filedescriptor
   return:      fd
   exceptions:  IOExcepiton
   comments:    Very often people complain about not being able to get past
                this function and it turns out to be permissions on the
                device file or bios has the device disabled.
----------------------------------------------------------*/
JNIEXPORT jint JNICALL RXTXPort(open)(
	JNIEnv *env,
	jobject jobj,
	jstring jstr
	)
{
	struct termios ttyset;
	int fd;

        const char filename = "/dev/ttyS0";

        do {
		fd=open (filename, O_RDWR | O_NOCTTY | O_NONBLOCK );
	}  while (fd < 0 && errno==EINTR);
	(*env)->ReleaseStringUTFChars( env, jstr, NULL );
	if( fd < 0 ) goto fail;

	if( tcgetattr( fd, &ttyset ) < 0 ) goto fail;
	ttyset.c_iflag = INPCK;
	ttyset.c_lflag = 0;
	ttyset.c_oflag = 0;
	ttyset.c_cflag = CREAD | CS8 | CLOCAL;

        ttyset.c_cc[ VMIN ] = 0;
	ttyset.c_cc[ VTIME ] = 0;

	if( cfsetispeed( &ttyset, B9600 ) < 0 ) goto fail;
	if( cfsetospeed( &ttyset, B9600 ) < 0 ) goto fail;

	if( tcsetattr( fd, TCSANOW, &ttyset ) < 0 ) goto fail;

	fcntl( fd, F_SETOWN, getpid() );
	fcntl( fd, F_SETFL, FASYNC );

	return (jint)fd;

fail:
	throw_java_exception( env, PORT_IN_USE_EXCEPTION, "open",
		strerror( errno ) );
	return -1;
}


Jan Newmarch (http://jan.newmarch.name)
jan@newmarch.name
Last modified: Mon Mar 10 05:19:05 EST 2003
Copyright ©Jan Newmarch
Copyright © Jan Newmarch, Monash University, 2007
Creative Commons License This work is licensed under a Creative Commons License
The moral right of Jan Newmarch to be identified as the author of this page has been asserted.