Java Native Interface. How you write C/C++/assembler
code native methods callable from Java. It also lets C/C++ code call Java
methods. Microsoft J++ does not support JNI. Before you get too deeply into JNI
check out the exec function. Also check if JConfig
has what you need. It may do what you want with much less hassle. Also check out XFunction
which lets you do JNI without writing any of that complicated JNI C code.
Introduction
Java Native Interface is how you write C/C++/assembler code
native methods callable from Java. It also lets C/C++ code call Java methods.
The Overall Process
-
If you need to use JNI from an Applet, order a certificate well in advance. See Signed
Applet.
-
Write a Java class containing a native method something like this Glue.java:
package com.mindprod.JNIexper;
import java.io.*;
public class Glue
{
static
{
System.loadLibrary( "Glue" );
}
public static native int widthInBits( int n );
}
-
Compile Glue.java. This step is very important. You
must have a clean compile before using javah.
-
Generate the Glue.h header file containing the
prototypes of the C/C++ methods you must write with:
javah.exe -jni -o Glue.h com.mindprod.JNIExper.Glue
-
Write a C or C++ class something like this Glue.c
#include <windows
.h
>
#include "Glue.h"
/**
* Wrapper C native method.
* Calculate how many bits wide a number is,
* i.e. position of highest 1 bit.
* @return p where 2**p is first power of two >= n.
* e.g. binary 0001_0101 -> 5, 0xffffffff -> 32,
* 0 -> 0, 1 -> 1, 2 -> 2, 3 -> 2, 4 -> 3
*
* public static native int widthInBits( int n);
*
* Class: cmp_JNIexper_Glue
* Method: widthInBits
* Signature: (I)I
*/
JNIEXPORT
jint JNICALL Java_cmp_JNIexper_Glue_widthInBits
(JNIEnv
*env
,
jclass thisClass
, jint n
)
{
...
return n;
}
-
Link that C code and any assembler code it calls into a DLL. The dll may contain
methods from many different classes.
-
Pre-install the DLL on the browser's classpath, e.g. J:\j2sdk1.4.2_04\jre\bin\ext
(for the JDK 1.4 plug-in), WINNT\java\trustlib (for
Internet Explorer), Program Files\Netscape\Communicator\java\classes
(for Netscape 7.02).
-
Use the Java native method as if it were an ordinary Java method.
Some general JNI tips
-
See if you can avoid JNI altogether. Let your Java talk to your native code via
TCP/IP.
-
JNI and web-loaded Applets are incompatible, even if you sign them or jump
through incredible hoops. You must use a Signed
Applet to install an second signed one on the client's local hard disk, and
then use that second Applet to do your JNI work. Even then loadLibrary
is unreliable.
-
A JNI call is very slow, in the order of .5 to 1.0 microseconds, the equivalent
of pages of linear Java code to do a simple method call. You would think there
would be a tiny generated machine code thunk to bridge between Java and C. Not
so -- at least in any JVM I know of, Java branches to some general purpose code
that interpretively constructs the C parameters. This code is not highly
optimised. It seems Sun wants to strongly discourage you from using native
methods just for speed. This means you don't want to hop back and forth between
Java and C, but rather to go to C, and stay there a decently long time before
returning. This means that you can't use C to speed up short operations, only
long ones, because of the overhead tacked on in getting to C wipes out any
savings.
-
Keep your JNI interface as minimal as possible. This is tricky, messy, kludgy
stuff. Do as little native code as possible. Confine it to as few classes as
possible.
-
Get your native code working first in an application before you tackle the
complexities of signed Applets and capabilities
and permissions. With applications you can control the loadlibrary path with:
java -Djava.library.path=. HelloWorld.
With Applets, you are at the mercy of the browser.
-
If you do your native code in C++, there is slightly more type checking. Watch
out, you use env-> in C++ rather than (*env)->
you would use in C. Keep aware in C++ when the first argument is hidden.
-
When you compile, set the Options/Project/Directory include path to: J:\j2sdk1.4.2_04J:\j2sdk1.4.2_04\include\win32,
or equivalent. Note that JNIEXPORT defined in win32\jni_md.h.
For Borland, make sure target expert says: DLL, Win32 console, static and
Project/Options/Linker/General is no debug, otherwise you will generate enormous
DLLs.
-
None of the Java safety net is operational when your native code is running.
Keep in mind the tiniest error in your JNI code will crash the JVM. In JDK 1.2
there is a debug mode that does extra parameter checking invoked with -Xcheck:jni.
Build your code incrementally, testing thoroughly before adding a few more lines.
-
Check the return status of every call. There is no exception mechanism to do it
for you.
-
Make sure you understand global and local references, and include the necessary DeleteLocalRef
and DeleteGlobalRefs. When you return an Object, you only
need to pass back a local reference. If you want to retain a long-term
reference to the Object for yourself, that lives after your method returns, then
you need to convert it to a global reference. Local references are
automatically deleted when your method returns. You must eventually delete
global references explicitly. You may optionally delete local references
prematurely. Similarly, you must explicitly use ReleaseStringUTFChars
to free the C-style string created from a Java Unicode string that you allocated
with GetStringUTFChars. If you fail to release, your code
will run for a while, then eventually die of the memory leak. These can be a
bear to track down. Code carefully and deal with the release the instant you
write the code for the allocate.
-
Be careful with GetIntArrayRegion and GetIntArrayElements
and modifying the C-array you get. Since GetIntArrayRegion
gives you a copy, any changes you make will not affect the Java array. GetIntArrayElements
effectively gives you a direct pointer to the Java array so any changes you make
to the C array are reflected in the Java array. However, there is a slight catch.
GetIntArrayElements may be implemented two ways:
-
Giving you a direct pointer to the Java array. In that case, any changes you
make are instantly reflected in the Java array. This technique is used when the
garbage collector supports pinning -- freezing the Java array at a fixed
location, even through a gc cycle.
-
Giving you a copy of the Java array. In that case, any changes you make are not
reflected until you call ReleaseIntArrayElements, when your C array is
automatically copied back into the Java array. This technique is used when the
garbage collector does not support pinning and there would be no way to freeze
the Java array at a fixed location where C expects it.
-
AllocObject does not invoke the constructors. It is up to you to
initialise all the fields. Normally you would use NewObject.
-
When debugging DLLs, remember the client must sometimes have to reboot to force
Windows or NT to reload the new version of the DLL. DLL hell strikes again. Why
must Microsoft fool around with dancing paperclips instead of fixing this? One
way around the problem is to keep changing the name of your DLL.
-
When you are debugging, display the code and DLL version number on the screen so
that you can detect if you are inadvertently using an old version of your Applet
stuck in cache, or an old version of a DLL.
-
If you have a class file twice on the classpath you will get a misleading error: java.lang.ClassFormatError:
Class already loaded. It has nothing to do with the DLL already being
loaded. Thankfully, having a DLL still loaded from the last time you ran is not
considered an error.
-
It is also probably a good idea to give your class containing the native method,
your C code, and the DLL distinct names.
-
When you do your javah -jni com.mindprod.mypackage.MyClass,
make sure you use dots in the name, not slashes.
-
When you do your javah -jni com.mindprod.mypackage.MyClass,
make sure you have freshly compiled it first. It works from the *.class file,
not the *.java file.
-
listdlls is a useful
tool to find out which NT DLLs are currently loaded.
-
Don't use thread local storage (a Microsoft C++ feature) with any DLLs that you
let the JVM load. TLS doesn't work if the library is loaded with loadLibrary.
-
I'm told you don't have to buy an expensive C/C++ compiler to do your JNI work.
The Gnu/Cygnus Windows ecgs
C++ compiler is available free for many platforms.
-
Though you can work in either C or C++, you always use extern 'C'
so that C-style parameter pushing order is always used.
-
Before you write your own JNI native class, check out JConfig. It may already be
done.
-
If you have a dual CPUs, JNI can be even more tricky. It has not yet been
properly tested in that configuration.
JNI Tips When Using Assembler
-
Using assembler or in-line assembler can massively shrink your DLL files. So
often in C, one tiny function brings in all kinds of library code.
-
If you work in assembler, you can get a rough prototype in ASM to work with by
writing a dummy version in C, and getting the compiler to generate ASM source.
In Borland you do this with Project | right click on *.c
file | Special | C->ASM.
-
Test your actual ASM code with a test C driver. Once you get that working, try
putting it in a DLL. After that is working, attempt calling it from Java.
-
Beware! assemblers may only handle 8.3 source names, and may truncate public
symbols or convert them to upper case. It gets pretty strange. Java, C, C++ and
MASM each have their own idea of what the symbol names are. Unless you specify
to the C++ compiler that symbols are extern 'C', it will
decorate them with method signatures. Sometimes symbols get a leading _.
You can guess these symbols from MAPs, error messages, and looking at hex dumps
of object files. I found it easiest to call a C wrapper method with the official
Java name which in turn calls an ordinary assembler routine with a short name,
all uppercase.
-
In theory, with a smart linker, you should be able to alias the short name to
the long one and avoid the C wrapper method. With the wrapper method, you don't
need the JNIEXPORT on your MASM prototype, since the
JNI interface never sees it. E.g. jint JNICALL NATIVE1(JNIEnv
*env, jclass thisClass, jint n);
-
Make sure you are explicit about JNICALL on your MASM prototype which means the
called routine is responsible for popping the parms with a ret
n.
-
Borland inline assembler does not work in the C compiler in 32-bit mode unless
you buy the separate TASM product.
-
You can't access the familiar 16-bit DOS int 21h functions, from a 32-bit flat
app.
-
The method signature strings that GetMethodID wants can
be had from running javap -s -p on your compiled Java
class files.
JNI Manipulator Functions
JNI gives you opaque access to the Java objects. You never touch the Java
objects directly, you always manipulate them via rather clumsy remote access
methods. It is bit like being a blind brain surgeon using barbeque tongs. The
advantage is your program never need know what the actual format of the objects
is. It makes it much easier to write portable C/C++ code.
| Useful JNI functions to Access Parameters |
| type |
get parm |
put parm |
release parm |
return |
Unicode String
converted to 16-bit chars
counted |
GetStringChars
GetStringLength |
- |
ReleaseStringChars |
NewString |
UTF-8 String
converted to 8-bit chars
null delimited |
GetStringUTFChars
GetStringLength |
- |
ReleaseStringUTFChars |
NewStringUTF |
| int |
use parm directly |
- |
- |
use local jint |
int[]
copy access |
GetIntArrayRegion
GetArrayLength |
SetIntArrayRegion |
- |
NewIntArray |
int[]
direct access |
GetIntArrayElements
GetArrayLength |
- |
ReleaseIntArrayElements |
NewIntArray |
| Object |
use parm directly |
- |
- |
NewObject
NewObjectA
NewObjectV |
Object[]
copy access |
GetObjectRegion
GetArrayLength |
SetObjectArrayRegion |
- |
NewObjectArray |
Object[]
direct access |
GetObjectArrayElements
GetArrayLength |
- |
ReleaseObjectArrayElements |
NewObjectArray |
| static field in Object |
GetStaticFieldID
GetStaticObjectField
GetStringUTFChars
GetStaticIntField |
setStaticObjectField
SetStaticIntField |
- |
- |
| instance field in Object |
GetFieldID
GetObjectField
GetIntField |
SetObjectField
SetIntField |
- |
- |
| callback static method |
FindClass
GetStaticMethodID
CallStaticVoidMethod
CallStaticIntMethod
CallStaticObjectMethod |
- |
- |
- |
| callback instance method |
FindClass
GetMethodID
CallVoidMethod
CallIntMethod
CallObjectMethod |
- |
- |
- |
In the above table, whereever you see "Int", you can replace it
with Boolean, Byte, Char, Short, Long, Float or Double. Note these methods do
not follow Java capitalisation conventions.
Typical JNI C Code
Here is some typical JNI C code to open a file, that lets you access a Java
string inside C.
JNIEXPORT jboolean JNICALL
Java_mypackage_myclass_mymethod
( JNIEnv * env, jclass callerclass, jstring filename )
...
HANDLE handle;
char * filename8
;
filename8 = (
*env)
->GetStringUTFChars(
env, filename
, NULL )
;
if ( filename8
== NULL )
{
return 0 ;
}
handle = CreateFile
(
filename8,
GENERIC_WRITE,
FILE_SHARE_READ||FILE_SHARE_WRITE
,
NULL,
OPEN_EXISTING,
0,
NULL
);
(*env
)->ReleaseStringUTFChars
( env,
filename, filename8
);
Using JNI in Applets
Using native methods in a Netscape Applet is a bear because even after you
manage to defang the security manager with:
PrivilegeManager.enablePrivilege( "UniversalLinkAccess" );
to let you call your native methods, System.loadLibrary("mydll")
and the undocumented security system, will insist that the *.DLL file containing
the your native methods and all your classes be pre-installed on the client's
machine. It refuses to look for them in the jar file or on the website where
CODEBASE points. You are pretty well stuck making your Applet install the
necessary DLLs and class files on the client's machine. Ouch! System.loadLibrary
fails for some reason if the DLL was not present at the time Netscape fired up.
The System.loadLibrary can't seem to see a DLL
installed dynamically. This makes no sense since the DLL is not loaded until System.loadLibrary
is called. Even more baffling is why System.load
would show the same behaviour. I have fooled around with this for months and I
still cannot get Netscape System.loadLibrary to
behave reliably and predictably. Until that problem is solved, it may be
practically impossible to use JNI from Applets.
I got word from Hannu Helminen that it is possible after all. He came across
this JavaWorld
Article. What is says basically that Netscape and native code do work
together after all. He tried it out, and to his amazement the example indeed
works! But hey, he did the same things that I did, where is the catch?
The idea is that first you download the native DLL and a class file to user's
local hard drive. The class file has to be in the Netscape class path, thus it
uses the system class loader. Also the DLL has to be downloaded and System.load()ed
before the class is referenced for the first time. It appears that
netscape does some kind of checking for the DLLs already when the class is
loaded. I have not yet had time to check this out myself.
To make it worse, there are fourteen security bypassing schemes you have to deal
with.
The Big Problem
Netscape won't let web-loaded Applets invoke DLL code, even if they have UniversalLinkAccess
permission. Further, it won't let them use a custom ClassLoader to do it
indirectly. You may bypass this with the undocumented MarimbaInternalTarget
class. See this link
for details. Your custom classloader must do a Class.getClass() first before
attempting to fulfill the request itself.
The Solution
You don't need to deal with any of this security, installing and jar-signing
stuff if you use an application instead of an Applet. I strongly suggest that
approach whereever possible.
I have fooled around with this over a period of six months, chasing wild goose
after wild goose, and have finally came to the conclusion, in agreement with Sun's
FAQ, that JNI and Applets simply don't mix. There is simply no way to get
sufficient security clearance to let you directly access the DLL from a web-loaded
Applet, even if you write a custom ClassLoader. One problem with doing so many
tests is I could have slipped somewhere along the line, thinking I tested two
cases, when I actually tested only one. The problem is the way Windows/Netscape
hold onto the old code. I have not even got the method I describe below to work.
It may fail too. Netscape security may apply even if you load from local hard
disk.
What you have to do is use a small signed installer Applet to install a second
unsigned Worker Applet on the client's local hard disk. When that second Worker
Applet runs, it is totally free of security restrictions, and so can access JNI
DLLs. It behaves much like an application, except it runs under a browser.
You also have to install some html in that same local directory that will load
the Worker Applet from the local hard disk. It would have CODE and ARCHIVE
parameters, but no CODEBASE. It defaults to the local hard disk directory where
the html file lives.
You have to install the DLL in C:\program files\Netscape\Communicator\program\java\bin,
which is guaranteed to be on Netscape's Windows path, where Windows looks for
DLLs.
You have to install the unsigned Worker jar file in C:\program files\Netscape\Communicator\program\java\classes.
Netscape totally trusts classes it loads from the local file system, even if
they are not signed and have no capabilities calls.
The Recipe
Just follow this recipe, if this discussion is making your brain hurt. The same
technique will work for other platforms with the obvious substitutions. If you
do understand it, you can create your own shortcuts.
In order to execute JNI methods from a Netscape Applet, create three jar files.
-
installer.jar. When this signed jar is first executed, it installs the
various files on the client's local hard disk, (intelligently choosing C: or D:).
On subsequent executions, it notices the needed files are already installed and
up-to-date and avoids that step. As soon as it has ensured it has installed the
files, it uses getAppletContext().showDocument(url)
to transfer control to the HTML it has downloaded on C: or D: The installer jar
contains only the tools such as FileTransfer
classes for help in downloading and installing the files. It does not contain
any of the data to be installed. Keeping that out of installer.jar saves
transferring that bulk when it is already installed.
-
Worker.jar is for your class files that contain native methods, and the
other classes you need to run the actual Applet. This jar should not be signed.
If you sign it, it will slow class loading the code down. The Worker.jar will be
embedded inside the toInstall jar, described shortly. The installer Applet will
copy the Worker.jar to C:\program files\Netscape\Communicator\program\java\classes
or perhaps D:. Thereafter, you could run it standalone via a bookmark, or you
could run it via the original install.jar. The advantage of using the original
install.jar is automatic updates, and automatic finding where the Worker.jar
Applet is installed. The disadvantage is extra startup time and an extra
annoying grant to run the program each time.
-
toInstall.jar. This unsigned jar is just a container for the various
filess you need to download namely:
-
An HTML file to invoke the actual Applet. Your installer Applet will install it
in: C:\program files\Netscape\Communicator\program\java\classes,
or perhaps D:.
-
Your DLL file containing the native C++/C/Assembler code. Your installer Applet
will install it in C:\program files\Netscape\Communicator\program\java\bin,
or perhaps D:.
-
Your worker Applet jar containing all the class files you will need to run the
unsigned Applet. Your installer Applet will install it in C:\program files\Netscape\Communicator\program\java\classes
or perhaps D:.
If you use getResourceAsStream, you must use the
goofy extension *.ram for resources inside your jar
files because Netscape interferes with the extensions DLL, EXE, CLASS etc. If
you access them via ZipFile that kludge is not
necessary.
System.load vs System.loadLibrary
System.load takes a fully qualified filename, e.g.
one ending in .dll. System.loadLibrary
takes an unqualified filename, and appends the .dll
for you. The idea is you can write more platform independent code this way. I
have had more success with System.load. Check out
the system property java.library.path to see where System.loadLibrary
is looking for DLLs. You must explicitly load the corresponding DLL before using
any class inside it. On windows that library path is supposed to contains:
-
The Windows system directories.
-
The current working directory.
-
The entries is the PATH set environment variable.
It depends on Applet or application, which browser and the phases of the moon
what you will get.
Solaris
Make sure you put your jni shared object library on the LD_LIBRARY_PATH.
If your use System.loadLibrary( "dog"
), then you must name your library file with your compiled C++ code libdog.so.
JNI and Assembler
To write the JNI code partly in assembler, there are two approaches:
-
Use Microsoft Visual C++ inline assembler.
-
Use a traditional external 32-bit assembler, and create a little C glue code.
Microsoft C conventions return an int value in eax
or a long in edx:eax. You can learn the register
conventions by adding the /FA option to the project C++ settings and looking at
the generated *.ASM code for C++ or C programs.
Learning More
The Java World JNI
Overview is a good place to start. The definitive source of information is Sun's
JNI pages which includes Beth Stearn's JNI
tutorial and the JNI
FAQ.
My essay has only scratched the surface. You must have a
text book if you hope for any success with JNI.
 | Essential JNI, Java Native Interface |
| 0-13-679895-0 |
| Rob Gordon and Alan |
|
|
|
 | The Java Native Interface, Programmer's Guide and Specification |
| 0-201-32577-2 |
| Sheng Liang |
| Sun Microsystems. Does not cover Applet signing, or "obvious" JNI like accessing int parms, but he does explain many fine points well. A slim, indispensible, expensive book. |
|
|