Mac OS X JNI Revisitedby Will Iverson, author of Mac OS X for Java Geeks
Earlier this year, my book, Mac OS X for Java Geeks, was released. One section of the book that generated more questions and comments than almost any other was the section on JNI, or the Java Native Interface.
JNI is the standard Java mechanism for interfacing Java applications with legacy C code. Standard Java code is bound to C-based libraries, with no changes or modification in your Java code required as you move between platforms. Generally speaking, it's considered good form to do some sanity checking when you attempt to load native code, and have your Java application either continue (possibly missing some native functionality) or gracefully exit if there is a failure.
In this article, we'll revisit the process of building a JNI project using the latest JDK 1.4.1, Apple's Project Builder, and the latest Java tools from Apple. We'll assume that you have registered with Apple Developer Connection (ADC) and downloaded the latest tools (as shown in Figure 1). After we look at building a JNI example, we'll look at some questions sent in by readers of the book.
Figure 1. Developer Tools
You can register for ADC at http://developer.apple.com/membership/online.html.
I'm assuming you're running Mac OS X 10.2, a.k.a. Jaguar. You'll also want to be sure to have installed the recently released Java 1.4.1 Update 1. You can get this via Software Update, and for more information visit:
The first thing to do is launch Project Builder (found at /Developer/Applications/Project Builder).
Figure 2. New Project
First, select the File --> New Project menu item. You'll see the dialog shown in Figure 2. Scroll down and select Java JNI Application, and hit Next. Use the name
JNIExample141, which will automatically be saved in a folder in your home (
Figure 3. Output
The Assistant will then create the project and take a few moments to generate an index. When it's done, select Build --> Build and Run, and you'll see the output as shown in Figure 3.
What's Going On?
In order to understand what's going on here, we'll need to explore the build process a bit. First, we'll look at the files included by default. You'll see several files listed. The main ones of interest are JNIWrapper.java and JNIExample141jnilib.c. These are the only files which are edited by hand -- you'll be putting your Java logic in the JNIWrapper.java file, and the C code in the JNIExample141jnilib.c file.
Figure 4. Files
In brief, you first specify the native code in the JNIWrapper.java file by marking a method with the
native keyword. This Java code is then compiled and fed to the javah tool, which produces a header (which you can see under the Products turn-down). You then find the implementation of the binding in the C file. This C file is then built into a library that the Java application loads and links against when you run.
Figure 5. Targets
You can see (and change the configuration for) this build process by clicking on the Targets tab, as shown in Figure 5. The main target,
JNIExample141, calls the
JNIWrapper target (which compiles your Java source and builds a JAR file), then calls the
CreateHeaders target (which calls javah, passing in the JAR file), and then uses JNILib to compile the C source into a library.
Note that javah only generates headers -- it does not generate the corresponding C source files. The Project Builder New Project Assistant generates the C files for you automatically, but only when you first create the project -- you'll need to create the C files by hand, and if you add new methods, you'll need to add those by hand as well. Alternatively, you can generate source files by passing a
-stubs as an option to javah, but be careful that you don't overwrite existing files.
One thing you may notice, comparing the latest version of the source generated by the Project Assistant to the example provided in Mac OS X for Java Geeks is that the sample source no longer generates an example of a call into a
dylib, just a local call into the
jnilib. This can be easily added back -- the base source does not demonstrate calling into the
dylib, but does generate the
dylib example header and C file. Just add
#include "JNIExample141dylib.h" just below
#include "JNIWrapper.h" in JNIExample141jnilib.c, and add JNIExample141dylib.c to your project.
Here's a couple of questions sent in regarding JNI and Mac OS X in response to Mac OS X for Java Geeks, with a brief response:
Question 1: Blending JDK Versions?
Q1: Using OS X 10.2.6 with Project Builder 2.1 and Java 1.4 downloaded (despite telling it to use JDK 1.3), while building the JNILib it reports an error that it cannot find jni.h.
A1: I'm using the latest Project Builder (2.1) and have also downloaded and installed the Java tools update from Apple's web site (see Figure 1). I'm not sure how you told Project Builder to use JDK 1.3 instead of JDK 1.4, as the system defaults to using JDK 1.4 for all of the tools (java, javac, javah, etc.). You can specify that you should use a specific version of the tool by pointing directly to the tool path. For example, use:
This will indicate to use JDK 1.3.1 instead of JDK 1.4.1 (for a full look at Apple's JVM installation layout, see my book, Mac OS X for Java Geeks). It sounds to me like you haven't completely specified JDK 1.3.1 commands throughout your Project Builder configuration, so check all of your targets. I'd suggest using the
-version argument for your various tools and scripts to confirm that the proper tool is being specified. That should help you diagnose your problems.
Question 2: Javah Output Inconsistent?
Q2: Javah does not create the
JNIEXPORT line for the
native function with the digit
"1" prepended to the method name -- this appears in several documents besides this book, but it isn't what javah produces. At least, not the javah that came with my version of Java 1.4.1 from Apple.
A2: The javah that I've got does this consistently for this specific method and signature (i.e., the method signature
native int native_method(String arg); consistently produces the result:
/* * Class: JNIWrapper * Method: native_method * Signature: (Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_JNIWrapper_native_1method (JNIEnv *, jobject, jstring);
Different methods and different signatures produce different results. For example,
native int foo(); produces:
/* * Class: JNIWrapper * Method: foo * Signature: ()I */ JNIEXPORT jint JNICALL Java_JNIWrapper_foo__ (JNIEnv *, jobject);
This is just one symptom of the basic differences between object-oriented programming in Java and procedural programming in C.
Question 3: JNIEnv Missing?
Q3: The Mac version of jni.h defines the service methods like
GetStringUTFChars() without the
JNIEnv item as the first argument on these functions -- if you construct
your code this way you get errors from gcc. jni.h defines wrappers that call the
underlying functions with the
JNIEnv item as the first argument.
A3: As of this writing, the version of jni.h that I'm using includes:
jsize (JNICALL *GetStringUTFLength) (JNIEnv *env, jstring str);
I'm not sure which version of jni.h you're using, so it's hard to be sure where the mismatch is occurring.
Question 4: C Files Not Updated?
Q4: You might also want to comment on the construction of the method name in the
JNIEXPORT line when the class containing the method is within a package. It turns
out that the call to the function includes the package name, but javah doesn't
account for this. So, when method bar in class
foo inside package
mumble is called,
the method name invoked is
Java_mumble_foo_bar(JNIEnv * env, .... ). If the C code
isn't constructed this way the call won't be resolved.
A4: I'm not sure if you're talking about generating C code via javah
-stubs, or if you're talking about the code generated by the Project Builder Assistant.
The Project Builder New Project Assistant doesn't generate Java code with a package statement. You can just add a package statement to the Java source and rebuild without complaint, but Project Builder doesn't actually move your source into the proper directory structure or update your C code to match the javah code incrementally generated.
Generally speaking, this is a common problem with Wizards and Assistants in a variety of tools. In this case, the javah tool is regenerating the headers, but not updating your C code to match (
-stubs will generate stub C code, but this could overwrite your existing C source). You can generate a lot of code very quickly, but it can be hard to understand what's going on without spending a lot of time looking at everything generated in detail. Treat these as powerful learning tools, tutorials that show you how everything is supposed to look, and try recreating the project from scratch.
Some of you may have noticed that I didn't touch on Xcode, Apple's upcoming development tool. Simply put, it hasn't been released yet, so I can't write about it. You can find a bit about it at:
For readers looking for more information on JNI, I suggest the following links:
Good luck, and may your bugs always be easy to reproduce.
Will Iverson has been working in the computer and information technology field professionally since 1990.
O'Reilly & Associates recently released (April 2003) Mac OS X for Java Geeks.
Sample Chapter 10, QuickTime for Java, is available free online.
For more information, or to order the book, click here.
Return to ONJava.com.