Previous Page TOC Index Next Page Home

Day20

Native Methods and Libraries

by Charles L. Perkins

Today, you'll learn all the reasons you might (or might not) want to write native methods in Java, about all of Java's built-in optimizations, and about the tricks you can use to make your programs faster. You'll also learn the procedure for creating, making headers and stubs for, and linking native methods into a dynamically loadable library.

Let's begin, however, with the reasons that you might want to implement native methods in the first place.

There are only two reasons that you might need to declare some of your methods native, that is, implemented by a language other than Java.

The first, and by far the best reason to do so, is because you need to utilize a special capability of your computer or operating system that the Java class library does not already provide for you. Such capabilities include interfacing to new peripheral devices or plug-in cards, accessing a different type of networking, or using a unique, but valuable feature of your particular operating system. Two more concrete examples are acquiring real-time audio input from a microphone or using 3D "accelerator" hardware in a 3D library. Neither of these is provided to you by the current Java environment, so you must implement them outside Java, in some other language (currently C or any language that can link with C).

The second, and often illusory reason to implement native methods, is speed—illusory, because you rarely need the raw speeds gained by this approach. It's even more rare to not be able to gain that speed-up in other ways (as you'll see later today). Using native methods in this case takes advantage of the fact that, at present, the Java release does not perform as well as, for example, an optimized C program on many tasks. For those tasks, you can write the "needs to be fast" part (critical, inner loops, for example) in C, and still use a larger Java shell of classes to hide this "trick" from your users. In fact, the Java class library uses this approach for certain critical system classes to raise the overall level of efficiency in the system. As a user of the Java environment, you don't even know (or see) any side effects of this (except, perhaps, a few classes or methods that are final that might not be otherwise).

Disadvantages of native Methods

Once you decide you'd like to, or must, use native methods in your program, this choice costs you dearly. Although you gain the advantages mentioned earlier, you lose the portability of your Java code.

Before, you had a program (or applet) that could travel to any Java environment in the world, now and forever. Any new architectures created—or new operating systems written—were irrelevant to your code. All it required was that the (tiny) Java Virtual Machine (or a browser that had one inside it) be available, and it could run anywhere, anytime—now and in the future.

Now, however, you've created a library of native code that must be linked with your program to make it work properly. The first thing you lose is the ability to "travel" as an applet; you simply can't be one! No Java-capable browser currently in existence allows native code to be loaded with an applet, for security reasons (and these are good reasons). The Java team has struggled to place as much as possible into the java packages because they are the only environment you can count on as an applet. (The sun packages, shipped primarily for use with stand-alone Java programs, are not always available to applets.)


Note: Actually, any classes that anyone writes without native code should be able to be loaded with an applet, as long as they depend only on the java packages. Unfortunately, many of the sun packages contain classes that must use native code to provide crucial, missing functionality from the java packages. All these missing pieces, and some additional multimedia and sound capabilities, will be added to the java packages in the future. (This has been informally promised in discussions I've had with the Java team.)

Losing the ability to travel anywhere across the Net, into any browser written now or in the future, is bad enough. What's worse, now that you can't be an applet, you have further limited yourself to only those machines that have had the Java Virtual Machine ported to their operating system. (Applets automatically benefit from the wide number of machines and operating systems that any Java-capable browser is ported to, but now you do not.)

Even worse, you have assumed something about that machine and operating system by the implementation of your native methods. This often means that you have to write different source code for some (or all) of the machines and operating systems on which you want to be able to run. You're already forced, by using native methods, to produce a separate binary library for every machine and operating system pair in the world (or at least, wherever you plan to run), and you must continue to do so forever. If changing the source is also necessary, you can see that this is not a pleasant situation for you and your Java program.

The Illusion of Required Efficiency

If, even after the previous discussion, you must use native methods anyway, there's help for you later in today's lesson—but what if you're still thinking you need to use them for efficiency reasons?

You are in a grand tradition of programmers throughout the (relatively few) ages of computing. It is exciting, and intellectually challenging, to program with constraints. If you believe efficiency is always required, it makes your job a little more interesting—you get to consider all sorts of baroque ways to accomplish tasks, because it is the efficient way to do it. I myself was caught up in this euphoria of creativity when I first began programming, but it is creativity misapplied.

When you design your program, all that energy and creativity should be directed at the design of a tight, concise, minimal set of classes and methods that are maximally general, abstract, and reusable. (If you think that is easy, look around for a few years and see how bad most software is.) If you spend most of your programming time on thinking and rethinking these fundamental goals and how to achieve them, you are preparing for the future. A future where software is assembled as needed from small components swimming in a sea of network facilities, and anyone can write a component seen by millions (and reused in their programs) in minutes. If, instead, you spend your energy worrying about the speed your software will run right now on some computer, your work will be irrelevant after the 18 to 36 months it will take hardware to be fast enough to hide that minor inefficiency in your program.

Am I saying that you should ignore efficiency altogether? Of course not! Some of the great algorithms of computer science deal with solving hard or "impossible" problems in reasonable amounts of time—and writing your programs carelessly can lead to remarkably slow results. Carelessness, however, can as easily lead to incorrect, fragile, or nonreusable results. If you correct all these latter problems first, the resulting software will be clean, will naturally reflect the structure of the problem you're trying to solve, and thus will be amenable to "speeding up" later.


Note: There are always cases where you must be fanatical about efficiency in many parts of a set of classes. The Java class library itself is such a case, as is anything that must run in real-time for some critical real-world application (such as flying a plane). Such applications are rare, however.

When speaking of a new kind of programming that must soon emerge, Bill Joy likes to invoke the four S's of Java: small, simple, safe, and secure. The "feel" of the Java language itself encourages the pursuit of clarity and the reduction of complexity. The intense pursuit of efficiency, which increases complexity and reduces clarity, is antithetical to these goals.

Once you build a solid foundation, debug your classes, and your program (or applet) works as you'd like it to, then it's time to begin optimizing it. If it's just a user interface applet, you may need to do nothing at all. The user is very slow compared to modern computers (and getting relatively slower every 18 months). The odds are that your applet is already fast enough—but suppose it isn't.

Built-In Optimizations

Your next job is to see whether your release supports turning on the "just-in-time" compiler, or using the java2c tool.

The first of these is an experimental technology that, while a method's bytecodes are running in the Java Virtual Machine, translates each bytecode into the native binary code equivalent for the local computer, and then keeps this native code around as a cache for the next time that method is run. This trick is completely transparent to the Java code you write. You need know nothing about whether or not it's being done—your code can still "travel" anywhere, anytime. On any system with "just-in-time" technology in place, however, it runs a lot faster. Experience with experimental versions of this technology shows that, after paying a small cost the first time a method is run, this technique can reach the speed of compiled C code.


Note: More details on this technique, and the java2c tool, will be presented tomorrow. As of the 1.0 release, neither of these tools are in the Java environment, but both are expected in a later release (perhaps 1.1).

The java2c translator takes a whole .class file full of the bytecodes for a class and translates them (all at once) into a portable C source code version. This version can then be compiled by a traditional C compiler on your computer to produce a native-method-like cached library of fast code. This large cache of native code will be used whenever the class's methods are called, but only on the local computer. Your original Java code can still travel as bytecodes and run on any other computer system. If the virtual machine automatically takes these steps whenever it makes sense for a given class, this can be as transparent as the "just-in-time" technology. Experience with an experimental version of this tool shows that fully optimized C performance is achievable. (This is the best anyone can hope to do!)

So you see, even without taking any further steps to optimize your program, you may discover that for your release of Java (or for releases elsewhere or coming in the near future), your code is already fast enough. If it is not, remember that the world craves speed. Java will only get faster, the tools will only get better. Your code is the only permanent thing in this new world—it should be the best you can make it, with no compromises.

Simple Optimization Tricks

Suppose that these technologies aren't available or don't optimize your program far enough for your taste. You can profile your applet or program as it runs, to see in which methods it spends the most time. Once you know this crucial information, you can begin to make targeted changes to your classes.


Tip: Use java -prof ... to produce this profile information. In an early release (and, presumably, some later release) the javaprof tool can "pretty-print" this information in a more readable format. (javaprof is not in the 1.0 release—but try the latest Java release's documentation for details.)

Before you begin making optimizations, you also may want to save a copy of your "clean" classes. As soon as computer speeds allow it (or a major rewrite necessitates it), you can revert to these classes, which embody the "best" implementation of your program.

First, identify the crucial few methods that take most of the time (there are almost always just a few, and often just one, that take up the majority of your program's time). If they contain loops, examine the inner loops to see whether they: call methods that can be made final, call a group of methods that can be collapsed into a single method, or create objects that can be reused rather than created anew each loop.

If you notice that a long chain of, for example, four or more method calls is needed to reach a destination method's code, and this execution path is in one of the critical sections of the program, you can "short-circuit" directly to that destination method in the topmost method. This may require adding a new instance variable to reference the object for that method call directly. This quite often violates layering or encapsulation constraints. This violation, and any added complexity, is the price you pay for efficiency.

If, after all these tricks (and the numerous others you should try that have been collected over the years into various programming books), your Java code is still just too slow, you will have to use native methods after all.

Writing native Methods

For whatever reasons, you've decided to add native methods to your program. You've already decided which methods need to be native, and in which classes, and you're rarin' to go.

First, on the Java side, all you need to do is delete the method bodies (all the code between the brackets—{ and }—and the brackets themselves) of each method you picked and replace them with a single semicolon (;). Then add the modifier native to the method's existing modifiers. Finally, add a static (class) initializer to each class that now contains native methods to load the native code library you're about to build. (You can pick any name you like for this library—details follow.) You're done!

That's all you need to do in Java to specify a native method. Subclasses of any class containing your new native methods can still override them, and these new (Java) methods are called for instances of the new subclasses (just as you'd expect).

Unfortunately, what needs to be done in your native language environment is not so simple.


Note: The following discussion assumes that C and UNIX are your language and environment. This means that some of the steps may differ slightly on your actual system, but such differences will be outlined in the notes surrounding the native method documentation in your release (in the document called "Implementing Native Methods" in the alpha, but folded into the programmer's tutorial in 1.0). The following discussion is purposely parallel to this documentation.

The Example Class

Imagine a version of the Java environment that does not provide file I/O. Any Java program needing to use the file system would first have to write native methods to get access to the operating system primitives needed to do file I/O.

This example combines simplified versions of two actual Java library classes, java.io.File and java.io.RandomAccessFile, into a single new class, SimpleFile:

public class  SimpleFile {
    public static final  char    separatorChar = '>';
    private protected    String  path;
    private protected    int     fd;
    public  SimpleFile(String s) {
        path = s;
    }
    public String  getFileName() {
        int  index = path.lastIndexOf(separatorChar);
        return (index < 0) ? path : path.substring(index + 1);
    }
    public String  getPath() {
        return path;
    }
    public native boolean  open();
    public native void     close();
    public native int      read(byte[]  buffer, int  length);
    public native int      write(byte[]  buffer, int  length);
    static {
        System.loadLibrary("simple");  // runs when class first loaded
    }
}

Note: The unusual separatorChar ('>') is used simply to demonstrate what an implementation might look like on some strange computer whose file system didn't use any of the more common path separator conventions. Early Xerox computers used '>' as a separator, and several existing computer systems still use strange separators today, so this is not all that farfetched.

SimpleFiles can be created and used in the usual way:

SimpleFile  f = new SimpleFile(">some>path>and>fileName");
f.open();
f.read(...);
f.write(...);
f.close();

The first thing you notice about SimpleFile's implementation is how unremarkable the first two-thirds of its Java code is! It looks just like any other class, with a class and an instance variable, a constructor, and two normal method implementations. Then there are four native method declarations. You'll recognize these, from previous discussions, as being just a normal method declaration with the code block replaced by a semicolon and the modifier native added. These are the methods you have to implement in C code later.

Finally, there is a somewhat mysterious code fragment at the very end of the class. You might recognize the general construct here as a static initializer. Any code between the brackets—{ and }—is executed exactly once, when the class is first loaded into the system. You take advantage of that fact to run something you want to run only once—the loading of the native code library you'll create later today. This ties together the loading of the class itself with the loading of its native code. If either fails for some reason, the other fails as well, guaranteeing that no "half-set-up" version of the class can ever be created.

Generating Header and Stub Files

In order to get your hands on Java objects and data types, and to be able to manipulate them in your C code, you need to include some special .h files. Most of these will be located in your release directory under the subdirectory called include. (In particular, look at native.h in that directory, and all the headers it points to, if you're a glutton for detail punishment.)

Some of the special forms you need must be tailored to fit your class's methods precisely. That's where the javah tool comes in.

Using javah

To generate the headers you need for your native methods, first compile SimpleFile with javac, just as you normally would. This produces a file named SimpleFile.class. This file must be fed to the javah tool, which then generates the header file you need (SimpleFile.h).


Tip: If the class handed to javah is inside a package, it prepends the package name to the header file name (and to the structure names it generates inside that file), after replacing all the dots (.) with underscores ( _ ) in the package's full name. If SimpleFile had been contained in a hypothetical package called acme.widgets.files, javah would have generated a header file named acme_widgets_files_SimpleFile.h, and the various names within it would have been renamed in a similar manner.

When running javah, you should pass it only the class name itself, and not the full filename, which has .class on the end.

The Header File

Here's the output of javah SimpleFile:

/* DO NOT EDIT THIS FILE - it is machine generated */ 
#include <native.h>
/* Header for class SimpleFile */
#ifndef _Included_SimpleFile
#define _Included_SimpleFile
struct Hjava_lang_String;
typedef struct ClassSimpleFile {
#define SimpleFile_separatorChar 62L
struct Hjava_lang_String *path;
long fd;
} ClassSimpleFile;
HandleTo(SimpleFile);
extern /*boolean*/ long SimpleFile_open(struct HSimpleFile *); 
extern void SimpleFile_close(struct HSimpleFile *);
extern long SimpleFile_read(struct HSimpleFile *,HArrayOfByte *,long); 
extern long SimpleFile_write(struct HSimpleFile *,HArrayOfByte *,long); 

#endif

Note: HandleTo() is a "magic" macro that uses the structures created at run-time by the stubs you'll generate later today.

The members of the struct generated above are in a one-to-one correspondence with the variables of your class.

In order to "massage" an instance of your class gently into the land of C, use the macro unhand() (as in "unhand that Object!"). For example, the this pseudo-variable in Java appears as a struct HSimpleFile * in the land of C, and to use any variables inside this instance (you), you must unhand() yourself first. You'll see some examples of this in a later section today.

Using javah -stubs

To "run interference" between the Java world of Objects, arrays, and other high-level constructs and the lower-level world of C, you need stubs.

Stubs are pieces of "glue" code that automatically translate arguments and return values back and forth between the worlds of Java and C.

Stubs can be automatically generated by javah, just like the headers. There isn't much you need to know about the stubs file, just that it has to be compiled and linked with the C code you write to allow it to interface with Java properly. A stubs file (SimpleFile.c) is created by running javah on your class by using the option -stubs.


Note: One interesting side-effect of stub generation is the creation of method signatures, informally called method descriptions elsewhere. These signatures are quite useful—they can be passed to special C functions that allow you to call back into the Java world from C. You can use stub generation to learn what these signatures look like for different method arguments and return values, and then use that knowledge to call arbitrary Java methods from within your C code. (Brief descriptions of these special C functions, along with further details, appear later today.)

The Stubs File

Here's the result of running javah -stubs SimpleFile:

/* DO NOT EDIT THIS FILE - it is machine generated */ 
#include <StubPreamble.h>
/* Stubs for class SimpleFile */
/* SYMBOL: "SimpleFile/open()Z", Java_SimpleFile_open_stub */
stack_item *Java_SimpleFile_open_stub(stack_item *_P_,struct execenv *_EE_) { 
    extern long SimpleFile_open(void *);
    _P_[0].i = SimpleFile_open(_P_[0].p); 
    return _P_ + 1;
}
/* SYMBOL: "SimpleFile/close()V", Java_SimpleFile_close_stub */
stack_item *Java_SimpleFile_close_stub(stack_item *_P_,struct execenv *_EE_) { 
    extern void SimpleFile_close(void *);
    (void) SimpleFile_close(_P_[0].p);
    return _P_;
}
/* SYMBOL: "SimpleFile/read([BI)I", Java_SimpleFile_read_stub */
stack_item *Java_SimpleFile_read_stub(stack_item *_P_,struct execenv *_EE_) { 
    extern long SimpleFile_read(void *,void *,long);
    _P_[0].i = SimpleFile_read(_P_[0].p,((_P_[1].p)),((_P_[2].i))); 
    return _P_ + 1;
}
/* SYMBOL: "SimpleFile/write([BI)I", Java_SimpleFile_write_stub */
stack_item *Java_SimpleFile_write_stub(stack_item *_P_,struct execenv *_EE_) { 
    extern long SimpleFile_write(void *,void *,long);
    _P_[0].i = SimpleFile_write(_P_[0].p,((_P_[1].p)),((_P_[2].i))); 
    return _P_ + 1;
}

Each comment line contains the method signature for one of the four native methods you're implementing. You can use one of these signatures to call into Java and run, for example, a subclass's overriding implementation of one of your native methods. More often, you'd learn and use a different signature to call some useful Java method from within C to get something done in the Java world.

You do this by calling a special C function in the Java run-time called execute_java_dynamic_method(). Its arguments include the target object of the method call and the method's signature. The general form of a fully qualified method signature is any/package/name/ClassName/methodName(...)X. (You can see several in the stub's output's comments, where SimpleFile is the class name and there is no package name.) The X is a letter (or string) that represents the return type, and the ... contains a string that represents each of the argument's types in turn. (Here are the letters (and strings) used, and the types they represent, in the example: [T is array of type T, B is byte, I is int, V is void, and Z is boolean).

The method close(), which takes no arguments and returns void, is represented by the string "SimpleFile/close()V" and its inverse, open(), that returns a boolean instead, is represented by "SimpleFile/open()Z." Finally, read(), which takes an array of bytes and an int as its two arguments and returns an int, is "SimpleFile/read([BI)I." (See the "Method Signatures" section in tomorrow's lesson for the full details.)

Creating SimpleFileNative.c

Now you can, at last, write the C code for your Java native methods.

The header file generated by javah, SimpleFile.h, gives you the prototypes of the four C functions you need to implement to make your native code complete. You then write some C code that provides the native facilities that your Java class needs (in this case, some low-level file I/O routines). Finally, you assemble all the C code into a new file, include a bunch of required (or useful) .h files, and name it SimpleFileNative.c. Here's the result:

#include "SimpleFile.h"     /* for unhand(), among other things */
#include <sys/param.h>      /* for MAXPATHLEN */ 
#include <fcntl.h>          /* for O_RDWR and O_CREAT */
#define LOCAL_PATH_SEPARATOR  '/'    /* UNIX */
static void  fixSeparators(char *p) { 
    for (;  *p != '\0';  ++p)
        if (*p == SimpleFile_separatorChar) 
            *p = LOCAL_PATH_SEPARATOR;
}
long  SimpleFile_open(struct HSimpleFile  *this) { 
    int   fd;
    char  buffer[MAXPATHLEN];
    javaString2CString(unhand(this)->path, buffer, sizeof(buffer)); 
    fixSeparators(buffer);
    if ((fd = open(buffer, O_RDWR | O_CREAT, 0664)) < 0)    /* UNIX open */ 
        return(FALSE);   /* or, SignalError() could "throw" an exception */ 
    unhand(this)->fd = fd;         /* save fd in the Java world */ 
    return(TRUE);
}
void  SimpleFile_close(struct HSimpleFile  *this) { 
    close(unhand(this)->fd);
    unhand(this)->fd = -1;
}
long  SimpleFile_read(struct HSimpleFile  *this, HArrayOfByte  *buffer, 
                                                                                                                                                _ long  count) {
    char  *data     = unhand(buffer)->body;  /* get array data   */ 
    int    len      = obj_length(buffer);    /* get array length */ 
    int    numBytes = (len < count ? len : count);
    if ((numBytes = read(unhand(this)->fd, data, numBytes)) == 0) 
        return(-1);
    return(numBytes);       /* the number of bytes actually read */ 
}
long  SimpleFile_write(struct HSimpleFile  *this, HArrayOfByte  *buffer,
                                                                                                                                                _ long  count) {
    char  *data = unhand(buffer)->body; 
    int    len  = obj_length(buffer);
    return(write(unhand(this)->fd, data, (len < count ? len : count))); 
}

Once you finish writing your .c file, compile it by using your local C compiler (usually called cc or gcc). On some systems, you may need to specify special compilation flags that mean "make it relocatable and dynamically linkable."


Note: If you don't have a C compiler on your computer, you can always buy one. You also could get a copy of the GNU C compiler (gcc), one of the best C compilers in the world, which runs on almost every machine and operating system on the planet. The best way to get gcc is to buy the "GNU release" on CD-ROM, the profits of which go to support the Free Software Foundation. You can find both the GNU CD-ROM and the Linux CD-ROM (which includes GNU) in select places that sell software or technical books, or you can contact the F.S.F. directly. The GNU CD-ROM is a bit pricey, and, though the Linux CD-ROM is very inexpensive, if you can't afford either, or want the latest version and already own a CD-ROM, you can download the gzip file ftp://prep.ai.mit.edu/pub/gnu/gcc-2.7.2.tar.gz, which contains all 7M of the latest gcc release. (If you'd like to make a donation to, or buy gcc or its manual from, the F.S.F., you can e-mail them at gnu@prep.ai.mit.edu or call 617.542.5942.)

Some Useful Functions

When writing the C code for native implementations, a whole set of useful (internal) macros and functions are available for accessing Java run-time structures. (Several of them were used in SimpleFileNative.c.)

Let's take a brief digression to understand some of them a little better.


Warning: Don't rely on the exact form given for any of the following macros and functions. Because they're all internal to the Java run-time, they're subject to change at any moment. Check to see what the latest versions of them look like in your Java release before using them.


Note: The following brief descriptions are taken from an alpha release of Java, because descriptions of them for the 1.0 release were not available as of this writing. How Java data types map into C types, and vice versa, will be detailed in future documentation. Refer to it for more details on that or on any of the sparsely documented items below. (Many are listed just to give you a taste of the capabilities of the available functions.)

The following:

Object  *unhand(Handle *)
int      obj_length(HArray *)

returns a pointer to the data portion of an object and returns the length of an array. The actual pointer type returned is not always Object *, but varies, depending on the type of Handle (or HArray).

While the following:

ClassClass    *FindClass(struct execenv  *e, char  *name, bool_t  resolve)
HArrayOfChar  *MakeString(char  *string, long  length)
Handle        *ArrayAlloc(int  type, int  length)

finds a class (given its name), makes an array of characters of length length, and allocates an array of the given length and type.

Use the function:

long  execute_java_dynamic_method(ExecEnv *e, HObject *obj, char *method_name,
                                                                                                                                _char *signature, ...);

to call a Java method from C. e is NULL to use the current environment. The target of the method call is obj. The method method_name has the given method signature. It can have any number of arguments and returns a 32-bit value (int, Handle *, or any 32-bit C type).

Use the following:

HObject  *execute_java_constructor(ExecEnv *e, char *classname, ClassClass *c,
                                                                                                                                _char *signature, ...);
long  execute_java_static_method(ExecEnv *e, ClassClass *c, char *method_name,
                                                                                                                                _char *signature, ...);

to call a Java constructor from C and call a class method from C. c is the target class; the rest are as in execut_java_dynamic_method.

Calling this:

SignalError(0, JAVAPKG "ExceptionClassName", "message");

posts a Java exception that will be thrown when your native method returns. It is somewhat like the Java code:

throw new ExceptionClassName("message");

Finally, here are some useful string functions:

void  javaStringPrint(Hjava_lang_String *s)
int   javaStringLength(Hjava_lang_String *s)
Hjava_lang_String  *makeJavaString(char  *string, int  length)
char  *makeCString(Hjava_lang_String *s)
char  *allocCString(Hjava_lang_String *s)
unicode  *javaString2unicode(Hjava_lang_String *s, unicode  *buf, int  len)
char     *javaString2CString(Hjava_lang_String *s, char     *buf, int  len)

The first two methods print a Java String (like System.out.print()), and get its length, respectively. The third makes a Java String out of a C string. The fourth and fifth do the reverse, turning a Java String into a C string (allocated from temporary or heap storage, respectively). The final two methods copy Java Strings into preexisting Unicode or ASCII C buffers.

Compiling the Stubs File

The final step you need to take in the C world is to compile the stubs file SimpleFile.c by using the same compilation flags you used for SimpleFileNative.c.


Note: If you have several classes with native methods, you can include all their stubs in the same .c file, if you like. Of course you might want to name it something else, such as Stubs.c, in that case.

You're now finished with all the C code that must be written (and compiled) to make your loadable native library.

A Native Library

Now you'll finally be able to tie everything together and create the native library, simple, that was assumed to exist at the beginning of today's lesson.

Linking It All

It's time to link everything you've done into a single library file. This looks a little different on each system that Java runs on, but here's the basic idea, in UNIX syntax:

cc -G SimpleFile.o SimpleFileNative.o -o simple

The -G flag tells the linker that you're creating a dynamically linkable library; the details differ from system to system.


Note: By naming the library simple, you're disobeying a UNIX convention that dynamic library names should have the prefix lib and the suffix .so (on your system, these prefixes and suffixes may differ). You can call your library libsimple.so to obey the convention, if you like, but just for the clarity of this example, the simpler name was used.

Using Your Library

Now, when the Java class SimpleFile is first loaded into your program, the System class attempts to load the library named simple, which (luckily) you just created. Look back at the Java code for SimpleFile to remind yourself.

How does it locate it? It calls the dynamic linker, which consults an environment variable named LD_LIBRARY_PATH that tells it which sequence of directories to search when loading new libraries of native code. Because the current directory is in Java's load path by default, you can leave "simple" in the current directory, and it will work just fine.

Summary

Today, you learned about the numerous disadvantages of using native methods, about the many ways that Java (and you) can make your programs run faster, and also about the often illusory need for efficiency.

Finally, you learned the procedure for creating native methods, from both the Java and the C sides, in detail—by generating header files and stubs, and by compiling and linking a full example.

After working your way through today's difficult material, you've mastered one of the most complex parts of the Java language. You now know how the Java run-time environment itself was created, and how to extend that powerful environment yourself, at its lowest levels.

As a reward, tomorrow we'll look "under the hood" to see some of the hidden power of Java, and you can just sit back and enjoy the ride.

Q&A

Q: What can I use to supplement the alpha release's "Implementing Native Methods" document you recommended?

A: Look at Sun's Java tutorial (online or on the CD-ROM included with this book) for a more detailed version of both the example in this book and its explanation. Today's discussion should be enough to get you started on your first native methods, however.

Q: Does the Java class library need to call System.loadLibrary() to load the built-in classes?

A: No, you won't see any loadLibrary() calls in the implementation of any classes in the Java class library. That's because the Java team had the luxury of being able to statically link most of their code into the Java environment, something that really makes sense only when you're in the unique position of providing an entire system, as they are. Your classes must dynamically link their libraries into an already-running copy of the Java system. This is, by the way, more flexible than static linking; it allows you to unlink old and relink new versions of your classes at any time, making updating them trivial.

Q: Can I statically link my own classes into Java like the Java team did?

A: Yes. You can, if you like, ask Sun Microsystems for the sources to the Java run-time environment itself, and, as long as you obey the (relatively straightforward) legal restrictions on using that code, you can relink the entire Java system plus your classes. Your classes are then statically linked into the system, but you have to give everyone who wants to use your program this special version of the Java environment. Sometimes, if you have strong enough requirements, this is the only way to go, but most of the time, dynamic linking is not only good enough, but preferable.

Q: My applet needs some key functionality, missing from the Java library. Given their many disadvantages, I'd like to avoid using native methods. Do I have any alternatives?

A: Because it's still early in the history of Java, a valid alternative to native methods is to try to convince the Java team that your needed capability is of interest to a broad range of future Java programmers; then they may include it directly into the java packages. There are already plans to do this with certain "missing" pieces of functionality, so this may not be as hard a sell as you might think. Start by posting some messages to the comp.lang.java newsgroup, to be sure no one else at Sun or elsewhere is already doing it, and then see what happens. This is a young, vibrant community of enthusiasts; you are not alone.

Previous Page TOC Index Next Page Home