Friday, June 24, 2011

Native Methods and Libraries - Java

What are Native Methods and Libraries?
Native methods and native libraries are bits of platform-specific executable code (written in languages such as C or C++) contained in libraries or DLLs. Inside your Java applications you can gain access to the functions inside those libraries, allowing you to create a sort of hybrid Java and native code application. Although using native methods can give you some extra benefits Java does not provide (such as faster execution or access to a large body of existing code), there are significant disadvantages in using native methods as well.

Why Use Native Methods?
  • Gaining access to special capabilities of your computer or operating system
  • Needing the extra speed that native methods provide
  • Needing access to a large body of existing code
Disadvantages of Native Methods

With a hybrid Java and native method program, however, you've given up that cross-platform capability. First of all, Java programs that use native methods cannot be applets. Period. For security reasons, applets cannot load native code. So if you use native methods, you've just removed the enormous number of users on the World Wide Web from your market.

Native code is, by definition, platform specific. The native code must exist on the platform your Java program is running on for that program to work. For your program to work on different platforms, you'll have to port your native code to that specific platform-which may not be a trivial task. And as new systems or new versions of operating systems appear, you may have to update or re-release new versions of that native code for every system. The write-it-once-run-it-everywhere advantage of Java ceases to exist when you use native methods.


Just-in-Time Compilers


Just-in-time compilers translate Java bytecode into native machine code on-the-fly as the bytecode is running. Depending on how good the JIT compiler is, you can often get very close to native execution speeds out of a standard Java program-without needing to use native code and without needing to make any modifications to your Java program-it just works.
The disadvantage, however, is that to get the speed increase your Java program must be run on a platform that has a JIT compiler installed. 

Writing Native Methods


In this section you'll learn the steps you must take to write your Java code so that it uses native methods, This involves four basic steps:
  • Write your Java code so that the methods that will be native have special declarations using the native modifier.
  • Compile your Java code and use the javah program to generate special header and stub files, which make up the starting point for your native code.
  • Write your native implementations of the native methods.
  • Compile all the native files into a shared library or DLL and run your Java program.
Write Your Java Code

The first step to implementing native methods is to decide which methods in which classes of your Java program will be native.
To declare that a method will be native inside your Java code, you add the native modifier to that method signature, like this:
public native void goNative(int x, int y);
 Note also that the native method in your Java code has no method body. Because this is a native method, its implementation will be provided by the native code, not by Java. Just add a semicolon to the end of the line.


The other change you'll have to make to your Java code is to explicitly load the native library that will contain the native code for these methods. To do this, you add the following boilerplate code to your Java class:
static {
    System.loadLibrary("libmynativelibrary.so");
}
This bit of code, called a static initializer, is used to run code only once when the class is first loaded into the system.The static initializer executes theSystem.loadLibrary() method to load in your native library as the class itself is being loaded. If the native library fails to load for some reason, the loading of the Java class fails as well, guaranteeing that no half-set-up version of the class can ever be created.
You can pick any name you want for your native library-here we've used the UNIX convention that libraries start with the word lib and end with the extension .so. For Windows systems, libraries typically end with the extension .DLL.
You can also use the System.load() method to load your native libraries. The difference is that the single argument to load() is the complete pathname to your native library, whereas the argument to loadLibrary() is just the library name.
And that's all you need to do in your Java code to create native methods and libraries. 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).
Listing 1 shows an example of a Java program called SimpleFile that was written to use native methods. This program might be used in a version of the Java environment that does not provide file input or output (I/O). Because file I/O is typically system-dependent, native methods must be used to implement those operations.


Listing 1. SimpleFile, a Java program that uses native methods.
1: public class  SimpleFile {
 2:     public static final  char    separatorChar = '>';
 3:     protected    String  path;
 4:     protected    int     fd;
 5: 
 6:     public  SimpleFile(String s) {
 7:         path = s;
 8:     }
 9: 
10:     public String  getFileName() {
11:         int  index = path.lastIndexOf(separatorChar);
12: 
13:         return (index < 0) ? path : path.substring(index + 1);
14:     }
15: 
16:     public String  getPath() {
17:         return path;
18:     }
19: 
20:     public native boolean  open();
21:     public native void     close();
22:     public native int      read(byte[]  buffer, int  length);
23:     public native int      write(byte[]  buffer, int  length);
24: 
25:     static {
26:         System.loadLibrary("simple");  // runs when class first loaded
27:     }
28: }

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 (getFileName() and getPath()). Then, in lines 20 through 23, there are four native method declarations, which are just normal method declarations 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, note the call to System.loadLibrary() in line 26, which loads a native library called simple. (We've intentionally violated library-naming standards here to make this example simpler.)

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. 

After you write the native part of your Java program, SimpleFile objects 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();
Generate Header and Stub Files

The second step to implementing native code is to generate a special set of header and stub files for use by your C or C++ files that implement those native methods. To generate these header and stub files, you use the javah program, which is part of the JDK (it's called JavaH in the Mac JDK).
First, you'll need to compile your Java program as you would any other Java program, using the Java compiler.

Header Files

To generate header files for a class, use the javah program with the name of the class file, minus the .class extension. For example, to generate the header file for the SimpleFile class, use this command line:
javah SimpleFile
To generate the header file for the SimpleFile class, drag-and-drop the class file onto the JavaH icon.
The file SimpleFile.h will be created in the same directory as the SimpleFile.class file.
Note that if the class you've given to javah is inside a package, javah prepends the package's full name to the header filename (and to the structure names it generates inside that file) with all the dots (.) replaced by underscores (_). If SimpleFile had been contained in a hypothetical package calledacme.widgets.filesjavah 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.

Listing 2 shows the header file that is generated by javah.

Listing 2. SimpleFile.h (a header file).
1: #include <native.h>
 2: /* Header for class SimpleFile */
 3: 
 4: #ifndef _Included_SimpleFile
 5: #define _Included_SimpleFile
 6: struct Hjava_lang_String;
 7: 
 8: typedef struct ClassSimpleFile {
 9: #define SimpleFile_separatorChar 62L
10:     struct Hjava_lang_String *path;
11:     long fd;
12: } ClassSimpleFile;
13: HandleTo(SimpleFile);
14: 
15: #ifdef __cplusplus
16: extern "C" {
17: #endif
18: extern /*boolean*/ long SimpleFile_open(struct HSimpleFile *);
19: extern void SimpleFile_close(struct HSimpleFile *);
20: extern long SimpleFile_read(struct HSimpleFile *,HArrayOfByte *,long);
21: extern long SimpleFile_write(struct HSimpleFile *,HArrayOfByte *,long);
22: #ifdef __cplusplus
23: }
24: #endif
25: #endif

There are a few things to note about this header file. First, note the struct ClassSimpleFile, which contains variables that parallel the instance variables inside your class. Second, note the method signatures at the end of the file; these are the function definitions you'll use in your C or C++ file to implement the actual native methods in the Java code.

Stub Files

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, which translate arguments and return values between Java and C.
Stubs are pieces of "glue" code that tie together Java and C. Stubs translate arguments and values and convert the various constructs in each language to something that can be understood in the other.
Stubs can be automatically generated by javah, just like headers. There isn't much you need to know about the stub file, just that it has to be compiled and linked with the C code you write to allow it to interface properly with Java.
To create stub files, you also use the javah program:
Use the javah program with the -stubs option to create the stub file:
javah -stubs SimpleFile
The file SimpleFile.c will be generated in the same directory as the class file.

Listing 3 shows the result of the stub file for the SimpleFile class.

Listing 3. SimpleFile.c (a stub file).
1:/* DO NOT EDIT THIS FILE - it is machine generated */
 2:#include <StubPreamble.h>
 3: 
 4:/* Stubs for class SimpleFile */
 5:/* SYMBOL: "SimpleFile/open()Z", Java_SimpleFile_open_stub */
 6:__declspec(dllexport) stack_item *Java_SimpleFile_open_stub(stack_item *_P_,
 7:    struct execenv *_EE_) {
 8:        extern long SimpleFile_open(void *);
 9:        _P_[0].i = (SimpleFile_open(_P_[0].p) ? TRUE : FALSE);
10:        return _P_ + 1;
11:}
12:/* SYMBOL: "SimpleFile/close()V", Java_SimpleFile_close_stub */
13:__declspec(dllexport) stack_item *Java_SimpleFile_close_stub(stack_item *_P_,
14:    struct execenv *_EE_) {
15:        extern void SimpleFile_close(void *);
16:        (void) SimpleFile_close(_P_[0].p);
17:        return _P_;
18:}
19:/* SYMBOL: "SimpleFile/read([BI)I", Java_SimpleFile_read_stub */
20:__declspec(dllexport) stack_item *Java_SimpleFile_read_stub(stack_item *_P_,
21:    struct execenv *_EE_) {
22:        extern long SimpleFile_read(void *,void *,long);
23:        _P_[0].i = SimpleFile_read(_P_[0].p,((_P_[1].p)),((_P_[2].i)));
24:        return _P_ + 1;
25:}
26:/* SYMBOL: "SimpleFile/write([BI)I", Java_SimpleFile_write_stub */
27:__declspec(dllexport) stack_item *Java_SimpleFile_write_stub(stack_item *_P_,
28:    struct execenv *_EE_) {
29:        extern long SimpleFile_write(void *,void *,long);
30:        _P_[0].i = SimpleFile_write(_P_[0].p,((_P_[1].p)),((_P_[2].i)));
31:        return _P_ + 1;
32:}

Implementing the Native Library

The last step, and the most difficult, is to write the C code for your native methods.
The header file generated by javah gives you the prototypes of the functions you need to implement to make your native code complete. You then write some C code that implements those functions and provides the native facilities that your Java class needs (in the case of SimpleFile, some low-level file I/O routines).
You'll want to include your header file as part of the initial includes for your native implementation:
#include <SimpleFile.h>


Listing 4 shows the native implementation of the methods from the SimpleFile class.

Listing 4. SimpleFileNative.c, a C implementation of a native method from SimpleFile
1: #include "SimpleFile.h"     /* for unhand(), among other things */
 2: 
 3: #include <sys/param.h>      /* for MAXPATHLEN */ 
 4: #include <fcntl.h>          /* for O_RDWR and O_CREAT */
 5: 
 6: #define LOCAL_PATH_SEPARATOR  '/'    /* UNIX */
 7: 
 8: static void  fixSeparators(char *p) { 
 9:     for (;  *p != '\0';  ++p)
10:         if (*p == SimpleFile_separatorChar) 
11:             *p = LOCAL_PATH_SEPARATOR;
12: }
13: 
14: long  SimpleFile_open(struct HSimpleFile  *this) { 
15:     int   fd;
16:     char  buffer[MAXPATHLEN];
17: 
18:     javaString2CString(unhand(this)->path, buffer, sizeof(buffer)); 
19:     fixSeparators(buffer);
20:     if ((fd = open(buffer, O_RDWR | O_CREAT, 0664)) < 0)    /* UNIX open */
21:         return(FALSE);   /* or, SignalError() could "throw" an exception */
22:     unhand(this)->fd = fd;         /* save fd in the Java world */ 
23:     return(TRUE);
24: }
25: 
26: void  SimpleFile_close(struct HSimpleFile  *this) { 
27:     close(unhand(this)->fd);
28:     unhand(this)->fd = -1;
29: }
30: 
31: long  SimpleFile_read(struct HSimpleFile  *this, 
32:     HArrayOfByte  *buffer, _ long  count) {
33:     char  *data     = unhand(buffer)->body;  /* get array data   */ 
34:     int    len      = obj_length(buffer);    /* get array length */ 
35:     int    numBytes = (len < count ? len : count);
36: 
37:     if ((numBytes = read(unhand(this)->fd, data, numBytes)) == 0) 
38:         return(-1);
39:     return(numBytes);       /* the number of bytes actually read */ 
40: }
41: 
42: long  SimpleFile_write(struct HSimpleFile  *this, 
43:     HArrayOfByte  *buffer,_ long  count) {
44:     char  *data = unhand(buffer)->body; 
45:     int    len  = obj_length(buffer);
46: 
47:     return(write(unhand(this)->fd, data, (len < count ? len : count))); 
48: }

Compile Everything into a Shared Library

The final step is to compile all the .c files, including the stub file and your native method files. Use your favorite C compiler to compile and link those two files into a shared library (a DLL on Windows). On some systems, you may need to specify special compilation flags that mean "make it relocatable and dynamically linkable." (Those flags, if they are required, may vary from system to system; check with your compiler documentation for details.)

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.

The resulting library should be the same name as you gave in your original Java class file as the argument to System.loadLibrary(). In the SimpleFile class, that library was called libmynativelibrary.so. You'll want to name the library that same name and install it wherever your particular system needs libraries to be installed.

Using Your Library

With all the code written and compiled and installed in the right place, all you have to do is run your Java program using the Java bytecode interpreter. When the Java class is loaded, it will also try to load the native library automatically; if it succeeds you should be able to use the classes in your Java class, and they will transparently run the native libraries as they are needed.
If you get an error that the library was not found, the most likely problem is that you do not have your environment set up correctly or that you have not installed your library in the right place.
DLL files are located according to the standard Windows algorithm: the directory the application was located in, the current directory, the System directory in Windows 95 (System32 in NT), the System directory in NT, the Windows directory, and then directories listed in the PATH environment variable.
UNIX systems use the environment variable LD_LIBRARY_PATH to search for libraries. This environment variable should include the standard places shared libraries are stored, as well as the current directory (.). After LD_LIBRARY_PATH has been set, Java will be able to find your library.
Shared libraries for Java must be stored in the folder System Folder: Extensions:JavaSoft Folder. Rather than copying your native library there, you can also just create an alias to your native library and put it in that folder.

No comments:

Post a Comment

How TOPT Works: Generating OTPs Without Internet Connection

Introduction Have you ever wondered how authentication apps like RSA Authenticator generate One-Time Passwords (OTPs) without requiring an i...