Java Native Interface
Java is a powerful object
oriented programming language. The real power of Java lies in its portability.
Java programs created on one platform can be run equally well on another
platform as long as a Java Virtual Machine (JVM) implementation is available
for that perticular platform. Java API (Application Programming Interface) is
another of Java’s strong points. The Java API provides most of the
functionality that a developer needs, however, there are situations where the
API can’t cater to the programmer’s needs. Java designers foresaw this problem
and provided an elegant workaround for such situations. The solution they
proposed was the Java Native Interface, commonly known as JNI.
JNI is sort of a bridge between
the Java programming language and the native environment for which the JVM is
implemented. Simply stated, you can incorporate native code (written in C/C++
or even assembly language) into your Java programs thus enhancing the
capabilities of Java. The question arises here, why do we need to incorporate
native code in Java programs at all?? Well, there are lots of reasons for this!
For example, in Java you can’t reference memory locations directly as you can
do in C or C++ (with pointers). Pointers are to the programming community, what
“friction” is to the mechanical engineers. You hate to have it, but you can’t
do without it!! It is impossible to write drivers for various hardware
interfaces without performing direct memory references. For example, the
printer port of a PC is usually mapped to the memory location 378
(hexadecimal). If you want to do anything with your printer port you need to
reference the memory location 0x378!! Similarly, character display memory in a
PC is mapped to the memory starting at 0xB80 (0xB00 for monochrome displays).
Referencing memory is not the
only situation where Java fails to provide support. There can be hundreds of
other situations where a Java programmer would be stuck if he didn’t have JNI
at his disposal. The standard Java API, for example, doesn’t provide any
password input stream for console applications (where you type in something and
only asterisks ‘*’ are echoed to the console).
Writing native methods for the Java Program involves the
following steps:
- Create a
Java class that declares the native method; this class contains the
declaration or signature for the native method.
- Compile
the Java class that declares the native method.
- Generate a
header file for the native method using the “javah” program provided with
the Java SDK.
- Write the
implementation of the native method in the programming language of your
choice, such as C, C++ or even assembly language.
- Compile
the header and implementation files into a shared library file (dll in
case of the windows platform).
- Run the
Java program.
Lets follow these steps and create a simple program to
demonstrate the usage of Java Native Interface. The program we will create will
provide us with a Password Input Stream to read private information from the
console.
STEP 1: Write the Java Program:
The Java program in this case is quite simple. It defines
two methods. It defines a native method readChar() and implements a method
readLine() that in turn calls the native method. The program is listed below:
public
class PasswordInputStream {
static {
System.loadLibrary("password");
}
public String readLine () {
String s = "";
int input = readChar();
while (input!=13) {
s = s + (char)input;
input = readChar();
}
System.out.println();
return s;
}
public native int readChar ();
}
The “loadLibrary”
method in the System class loads the Dynamically Linked Library (dll)
associated with this program. We’ll talk about these in a moment. The static
construct will be new to a lot of people. Well just think of it as a
substitution to the main method. Since there is no main method in this simple
program (we wish to use it as a utility class), so we’re using the “static”
construct.
The readLine() method reads chararacters from the console
using the native method readChar() and concatenates them into a string (by the
way, 13 is the ascii code for the return key in decimal).
The last line of the program provides the signature of the
readChar method. The keyword “native” indicates that the method will be
implemented in a native language and is thus not implemented in the program.
STEP 2: Compile the program:
Now that you’ve created the program, you need to compile
it in the usual manner. Use the “javac” compiler to compile it.
STEP 3: Generate a header file:
The next step is to generate a header file for the native
language. Header files can be thought of as interfaces with respect to Java.
These provide the native method prototypes and define various macros and
variables for the JNI. Our header file provides the C language signature for
the readChar() method.
You can write your own header file, but you’ll need to be
familiar with C/C++ to do that. JDK solves this problem with the “javah”
program. It automatically generates a header file for you. Run it by using the
command “javah PasswordInputStream” and it will create the file
“PasswordInputStream.h”. The file is listed below:
/*
DO NOT EDIT THIS FILE - it is machine generated */
#include
/*
Header for class PasswordInputStream */
#ifndef
_Included_PasswordInputStream
#define
_Included_PasswordInputStream
#ifdef
__cplusplus
extern
"C" {
#endif
/*
* Class:
PasswordInputStream
* Method:
readChar
* Signature: ()I
*/
JNIEXPORT
jint JNICALL Java_PasswordInputStream_readChar
(JNIEnv *, jobject);
#ifdef
__cplusplus
}
#endif
#endif
You don’t need to know too much about this file. Contrast
the Java method declaration:
public native int readChar ();
with the native signature:
JNIEXPORT
jint JNICALL Java_PasswordInputStream_readChar (JNIEnv *, jobject);
You can
see that the “int” data type is mapped to the “jint” data type in the native
signature. The name of the native language function that implements the
native method consists of the prefix Java_
, the package name, the class
name, and the name of the native method. Between each name component is an
underscore "_" separator.
Notice that the implementation of the native
function, as it appears in the header file, accepts two parameters even though,
in its definition on the Java side, it accepts no parameters. The JNI requires
every native method to have these two parameters.
STEP 4: Write the native implementation:
We are now ready to implement the readChar()
method in the native language (C in this case). The choice of the native
language is solely a choice of the developer, however, the language must have a
compiler, linker with the ability to create shared libraries (DLLs in case of
windows platform).
The C code is given below:
#include conio.h
#include stdio.h
#include jni.h
#include
"PasswordInputStream.h"
JNIEXPORT
jint JNICALL Java_PasswordInputStream_readChar (JNIEnv *env, jobject obj) {
int a = getch();
if (a!=13) {
printf("*");
}
return a;
}
For those of you not too familiar with C I’ll
briefly describe what this code means. The lines starting with the pound sign
(#) are pre-processor directives. The “include” directive simply inserts the
named file into the current file. You can do that by using a word processor and
not use the “include” directive at all!! We’re including the conio.h file
because it contains the prototype (signature) of the C library function,
“getch()”. Similarly, stdio.h contains the prototype for the “printf()” C
library function.
Jni.h provides information that the native
language code requires to interact with the Java runtime system. When writing native methods, you
must always include this file in your native language source files.
The method simply reads a character from the
console and if its not the carriage-return (ascii code 13 decimal) then prints
an asterisk to the console.
STEP 5: Compile the native program:
Now that you have implemented the method in the
native language, you need to compile it into a shared library. You’ll need a C
compiler that can create shared libraries. I personally use Borland’s C++
Builder. Microsoft’s Visual C/C++ compiler also has the capability to compile
as a shared library. Microsoft’s Quick C compiler I heard could compile to
shared libraries but it has poor documentation and online help and I couldn’t
find the switch for the purpose!! If you want to deploy the shared library to
some platform other than Microsoft windows, then you need to create a shared
library for that platform. You can create a shared library on a Unix/Linux
variant by using the “gcc” compiler.
You need to put “\include” and “\include\win32”
in the “include” path of your compiler in the windows environment.
The compiler will generate a .dll file. You need
to place this dll file in the directory where you java program is located or
put it in the “\bin” for global access.
STEP 6: Test/Run the program:
Finally you need to test the program that you
created. Lets create a simple test program to see if everything works as
expected. The test program is listed below:
public
class test {
public static void main (String [] args) {
PasswordInputStream pis = new
PasswordInputStream();
System.out.print("Password:
");
String passwd = pis.readLine();
System.out.println("You entered:
"+passwd);
}
}
It creates an
instance of the PasswordInputStream class and then uses the readLine instance
method to read a password. It then displays the password to the user (not a
smart move!!).
The discussion was meant to be an introduction to JNI.
There is definitely more to it and you can learn more by going through the
various articles and tutorials at http://java.sun.com.
If you read this article please vote for it. It'll take less than a minute of your time!!