Section 5.7. Going Native


5.7. Going Native

Now we come to the deeper darker mysteries. Let us take a look at javah. No, javah is not a Hebrew word. It is not some lost mystical text from the days before science supplanted magic. It is the Java C header and stub file generator.

If you are not already fairly experienced in writing, compiling, and building shared libraries in C on a Linux system, we would suggest that you skip this section, at least until you have developed intermediate skills in these areas. Otherwise, feel free to proceed.

We're going to walk you very quickly through building a Java native method here.[9] Don't worry if you don't quite follow it all. We will cover this topic at greater length elsewhere in the book. For now, we're giving you the highlights. Also be sure to check out Section 5.15. We'll point you to many additional resources on JNI (Java Native Interface) in that section.

[9] You might be tempted to call our comments in the introduction where we mentioned that we did not like purely pedagogical examples and that we would provide real, useful code. Well, we have to confess that there are some features of the Java language that we couldn't cram into our real-world examples. This JNI sample is one such. We admit our failure, and we apologize.

Sounds pretty intimidating, huh? Well, depending upon your background and experience, it can be a bit intimidating. As much as this will hurt some diehard Java purists, Java is not the right language for everything. Java's size and semiinterpreted nature in particular make Java ill-suited for the "close to the metal" tasks, such as device drivers and raw socket networking.

Fortunately, Java's designers were of this rocket-scientist breed (and so, for that matter, are your bending authors), so they gave Java programmers a back door: native methods. A native method is a class method whose name, arguments, and return type are declared in Java, but whose underlying implementation is written in "native code" (usually C, but it could be any compiled language that can match C's stack frame conventions).

As an example, let's implement a native method that will use the native Linux C library calls to get the current program's effective user ID and the name associated with that user.

First, we will write the Java class (Example 5.7).

You may never have seen code like that at the start of a class definition. The block declared

 static { ... } 

is called a static initializer and we'll discuss it in a moment.

Example 5.7. Java application with a native method (GetUser.java)
 public class GetUser {   static {     System.loadLibrary("getuser");   }   public native String getUserName();   public static void main(String[] args)   {     GetUser usr = new GetUser();     System.out.println(usr.getUserName());   } } 

Once you have the Java code, compile it with javac. You now have the compiled class. The next step is to use the javah tool to build the header file for your C code.

 $ javah GetUser 

Example 5.8 shows the header file thus produced.

Note that you run javah on the class file, not on the source file. The normal class name to classpath mappings apply. The file produced as a result is called, in this case, GetUser.h. The next step is to write the C code that implements the method (Example 5.9).

There's a lot going on here. First, the constant, L_cuserid, is defined in stdio.h; it represents the number of characters required to hold a user name. We're defining a char array to hold that number of characters plus one.[10] We are then calling the cuserid() function (see the manpage of cuserid(3)) to get the user name of the effective user ID of the process.

[10] What can we say? We're paranoid about the trailing null. Sue us.

That much is familiar C. But what is the argument list? Our method took no arguments. And what's with the functions being called through the pointer argument?

Example 5.8. Header file for GetUser native methods (GetUser.h)
 /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class GetUser */ #ifndef _Included_GetUser #define _Included_GetUser #ifdef __cplusplus extern "C" { #endif /*  * Class:     GetUser  * Method:    getUserName  * Signature: ()Ljava/lang/String;  */ JNIEXPORT jstring JNICALL Java_GetUser_getUserName   (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 

Example 5.9. Native method's C implementation file (GetUser.c)
 #include "GetUser.h" #include <stdio.h> JNIEXPORT jstring JNICALL Java_GetUser_getUserName(JNIEnv *jenv, jobject obj) {   char buffer[L_cuserid + 1];   cuserid(buffer);   return (*jenv)->NewStringUTF(jenv, buffer); } 

All of the Java class member data and Java class methods may be reached through the JNIEnv pointer argument. There are also methods provided by JNI itself. One of those is NewStringUTF(). Remember that Java Strings are Unicode, not 8-bit ASCII, so you must convert to and from Unicode (UTF-8 is an 8-bit encoding for Unicode that coincides with ASCII in the low 7 bits, so it is often used for such conversions). You can think of the JNIEnv as a C++ class pointer, or you can think of it as a structure of data and function pointers (that's really what a C++ class is, after all). The bottom line is, it provides the means to access and manipulate the Java environment from your native code.

The second argument, jobject, is the "this" pointer. It points to the GetUser class, and it is upcast to the JNI equivalent of the Java Object type. If our method took parameters, they would follow these two constant arguments.

JNI is a huge topic. You can read more about it in the Sun Microsystems JNI Tutorial,[11] or in Java 2 SDK JNI FAQ,[12] or in the JNI 1.1 Specification,[13] or in the associated JDK 1.2 Update[14] or the JDK 1.4 Update.[15]

[11] http://java.sun.com/docs/books/tutorial/native1.1/index.html

[12] http://java.sun.com/products/jdk/faq/jni-j2sdk-faq.html

[13] http://java.sun.com/products/jdk/1.2/docs/guide/jni/spec/jniTOC.doc.html

[14] http://java.sun.com/j2se/1.4.1/docs/guide/jni/jni-12.html

[15] http://java.sun.com/j2se/1.4.1/docs/guide/jni/jni-14.html

Even with all of this "We're too busy to explain things to you" going on here, we've got a lot more to cover before we are done. The next step in our little demo is to compile the C program and create a shared library of the code.

 $ cc -c GetUser.c $ cc -shared -o libgetuser.so GetUser.o $ export LD_LIBRARY_PATH=. 

The first line compiles the native method to a .o (object) file. The second command makes a shared library out of it. Now, refer back to the static initializer in Example 5.7. A static initializer is run before everything else in a class, even before main(). In this case, it uses the loadLibrary() method of the System class to load the shared library we just created. Note that library naming rules of the target OS are applied. The library is named getuser and on a Linux system it is assumed that that library will be in a file named libgetuser.so.

The last line sets an environment variable, LD_LIBRARY_PATH, to provide a path where Java will search for libraries. This is behavior inherited from Solaris. Linux uses ldconfig to maintain a list of shared libraries. Usually, a library is placed in a directory named in the file ld.so.conf and a memory cache of these libraries is built and maintained with the ldconfig program. The library loader in the JVM, however, works as the shared library system in Solaris, where the LD_LIBRARY_PATH is searched for shared libraries. If you try a JNI method and get library errors, check your LD_LIBRARY_PATH first. Here, we used ".", meaning "current directory." In practice, you wouldn't do this. You would deploy your shared library to a standard location and have LD_LIBRARY_PATH preset to that directory or directories. We just wanted to show you how it works here.

Let's see our class in action now.

 $ java GetUser mschwarz $ su Password: # export LD_LIBRARY_PATH=. # java GetUser root # exit exit $ 

To JNI or Not to JNI

We dislike religious debates. We have no desire to nail down what taints the purity of Java and what does not. A warning we do want to give you is, if you are an experienced UNIX C/C++ developer, you must resist the temptation to use JNI and native methods all over the place. The Java APIs are extensive, and there are probably classes that already do what you want to do. You will be tempted to use native methods because "you know how to do it in C." Resist. Find the Java way. JNI is a great way to introduce subtle and hard to find bugs into your Java programs. Leave that to the API and JVM coders. ;-)

That said, we don't want to discourage you from making use of JNI when it is the right way, or the only way, for what you need to do. The tool is there. Use it. Just remember what it does cost you in portability and what it may cost you in maintenance and debugging. Design decisions have costs and benefits. Try to find the balance.


Here you see the class being run, and, sure enough, it displays our username. We then run su to become root and (after setting that library path) run it againand, sure enough, it tells us we are "root."

We'll talk more about JNI later in the book, but now you know enough to be dangerous.



    Java Application Development with Linux
    Java Application Development on Linux
    ISBN: 013143697X
    EAN: 2147483647
    Year: 2004
    Pages: 292

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net