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
FileInputStream.read()
Runtime.totalMemory()
Thread.currentThread()
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
public class HelloWorld {
public native void sayHello();
}// HelloWorld
javac HelloWorld.java
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
#include <stdio.h>
#include "HelloWorld.h"
JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *env, jobject obj) {
printf("Hello world\n");
}
gcc -c -I/usr/local/src/jdk1.3/include \
-I/usr/local/src/jdk1.3/include/linux \
-fPIC hello.c
gcc -shared -W1 -o libhello.so hello.o
public class TestHello {
static {
System.loadLibrary("hello");
}
public static void main(String[] argv){
new HelloWorld().sayHello();
}
}// TestHello
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
java TestHello
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
C code written using JNI must also
Java type | C type |
---|---|
int | jint
|
boolean | jboolean
|
void | void
|
Java type | C type |
---|---|
Object | jobject
|
Class | jclass
|
String | jstring
|
array | jarray
|
Java type | C type |
---|---|
int[] | jintArray
|
boolean[] | jbooleanArray
|
Object[] | jobjectArray
|
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);
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;
}
}
The JNI has mechanisms to
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;
}