Wednesday, 13 May 2015

Using javah in Java

 One of the lesser known tools provided in the JDK is javah. This specialized tool creates two C files from your Java classes. These C files can be used to implement native methods. javah creates a header file and a "stub" file for the native methods of a class. The header file declares the functions that will implement the class methods. The stub file provides the glue that binds the Java method invocation and object references to the C code. It is up to you to provide another C file (called the implementation file) with the functions declared in the header. These three C files allow a Java class to call C functions. In turn, the C functions can reference the object's instance variables. Ultimately, these files (possibly with others) are compiled into a dynamically loadable library. 


Should I Use Native Methods?
Before you decide to use native methods, there are some things you should consider carefully. First of all, the native methods are in pure C code, not C++. The function prototypes provided by javah are in an object-oriented style of C, but they are still not really object methods. You will lose the benefits of inheritance and polymorphism. The stubs create some data structures that provide weak encapsulation, but it is up to you to enforce it.
Second, native methods can only be called from applications. Applets are prevented from calling native methods for security reasons.
Third, and most importantly, native methods are inherently platform-specific. You will have to build the dynamically loadable library for each platform your application targets. Of course, that means any system-specific code has to be ported, too. (I know, C is supposed to be a portable language, but isn't that why you are using Java?)
On the other hand, native methods are the only way to use any system features not provided by the Java Virtual Machine.

The Header File

When you create a header file from a Java class, the header contains two things: declarations for the C implementation functions and a structure definition. The layout of the structure matches the layout of the Java class. A pointer to an instance of the structure is passed to the implementation functions. By using the fields of the structure, the C functions can access the instance variables of the Java class.
Because of this mapping, whenever you change the instance variables of the Java class, you should regenerate the header file. Of course, that also means it will be best if you do not modify the header manually.
The Java class name determines the header filename and the name of the structure declaration. javah appends ".h" to the class name for the header filename. For the structure name, javah uses the class name with the word "Class" prepended. For example, if you run javah on a class named HelloWorld, it will produce a header file named HelloWorld.hwith a structure in it called ClassHelloWorld.

The Stub

The stub filename is just the class name with ".c" appended. For the sample class HelloWorld, javah would create a stub file named HelloWorld.c. At this point, just think of the stub functions as glue between the C implementation functions and the Java method invocations.
javah creates the stub functions directly from the class native methods, just as it creates the structure declaration in the header file from the instance variables. So, be sure to regenerate the stub file any time you change a native method in the class.
In this case, it is not just inconvenient to modify the stub file-it's a bad idea. Any changes you might want to make are probably best done in the Java code or in the implementation file, anyway. It really is best if you put the stub file aside because any changes you make here are likely to break the Java-to-C linkage.

Usage

The synopsis for javah is as follows:
javah [ options ] classname
javah_g [ options ] classname
javah uses slightly different options for the two platforms. In all cases, javah accepts multiple class names on the command line. javah_g produces files suitable for use with debuggers like jdb. It accepts identical options to javah.
Here are the command-line options for the Windows 95/NT version of javah and javah_g. (These are from version 1.0.2 of the JDK.)

Windows 95/NT
OptionDescription
-o outputfileThis option will force all of the headers or stubs for all of the classes to be placed into outputfile. Without this option, javah will create a separate file for each class.
-d directoryTells javah to put all output files in directory.
-td directoryTells javah to use directory for temporary files. Otherwise, javah checks the environment variable %TEMP%. If %TEMP% is not set, then javah checks %TMP%. If %TMP% is not set, javah falls back on C:\tmp, creating it if necessary.
-stubsTells javah to create the stub files. By default, javah creates the header files.
-verboseTells javah to print messages to stdout regarding the output files.
-classpath pathOverrides the default class path and the environment variable %CLASSPATH%. Path uses the same syntax as the CLASSPATH environment variable.


Windows 95/NT
Environment VariableDescription
CLASSPATHYou can set the environment variable CLASSPATH to provide the JDK a path to your user-defined classes. You can specify multiple directories separated by semicolons. Use the DOS-style backslash as the path component separator. Under JDK 1.0.2 and higher, the class path can include an archive file classes.zip, which will be searched for the classes given on the command line. It is a good idea to always start the class path with the current directory (for example, set CLASSPATH=.;C:\Java\Lib\classes.zip).
TEMP, TMPIf you do not use the -td option, javah uses %TEMP% and %TMP% to determine where to store temporary files. See the discussion of the -td option.


SPARC Solaris
OptionDescription
-o outputfileThis option will force all of the headers or stubs for all of the classes to be placed into outputfile. Without this option, javah will create a separate file for each class.
-d directoryTells javah to put all output files in directory.
-td directoryTells javah to use directory for its temporary files, instead of /tmp.
-stubsTells javah to create the stubs files. By default, javah creates the header files.
-verboseTells javah to print messages to stdout regarding the output files.
-classpath pathOverrides the default class path and the environment variable $CLASSPATH. path uses the same syntax as the CLASSPATH environment variable.
Here are the command-line options for the Solaris version of javahand javah_g. (These are from version 1.0.2 of the JDK.)

SPARC Solaris
Environment VariableDescription
CLASSPATHYou can set the environment variable CLASSPATH to provide the JDK a path to your user-defined classes. You can specify multiple directories separated by colons. Under JDK 1.0.2 and higher, the class path can include an archive file classes.zip, which will be searched for the classes given on the command line. It is a good idea to always start the class path with the current directory "." (for example, CLASSPATH=".:/java/lib/classes.zip").

Example

Now create a class and see how javah translates it into a header and stub file. For now, disregard the implementation file. Listing 14.1 shows a class that might be used as part of a stock portfolio charting application. The exact output may vary slightly depending on your target platform and the version of the JDK you use.

Listing 14.1. Initial PortfolioEntryclass.
class PortfolioEntry {
   String TickerSymbol;
   int         NumberOfShares; 
   float   LastQuote;
   float   BoughtAtPrice;
   float   LastDividends;
   float   LastEPS;

   public native void FetchQuote();
   public native float CurrentValue();
   public native float NetGain();
   public native float Yield();
};

The PortfolioEntry class has attributes for the current stock quote, number of shares owned, the buy price for these shares, the dividends paid last quarter, and the earnings per share reported for last quarter. It has methods to calculate the current value of the shares, the net profit or loss based on the current value, and the yield of the lot (net profit or loss per share as a percentage). It also has one crucial method to retrieve the latest quote for this stock. (For the sake of this example, assume that the dozens of Web-based quote services do not exist.) Notice the use of the nativekeyword. Without this modifier, this class will not compile, because we have not provided any method bodies.

Use and Abuse of the native Keyword
The keyword native modifies a method declaration. It indicates to javac that the body of the method will be provided by code native to the current platform. You can compose native with other method declaration modifiers such as static and abstract. How does javac interpret an abstract, native method? Not very well. Although you can provide a method body for an abstract native method, that body will never be called. A static native method behaves exactly as you would expect. Be careful about the object instance pointer passed to the native method, however. A static, native method is always passed a NULL for its argument.
Native methods that are overridden in a subclass work just as you would expect. In fact, you can override a native method with a Java method, or a Java method with a native method, or a native method with a native method. (Whew!)
You cannot make a constructor native, but then again, you cannot make a constructor static, synchronized, abstract, or final.
All commands in this example follow the syntax for the Windows 95/NT JDK version 1.0.
First, compile the Java code:
>javac PortfolioEntry.java
This will create the usual PortfolioEntry.classfile. Now, use javah to create the header file:
>javah PortfolioEntry
Note that javah only expects class names, not filenames. Therefore, you do not need to add .javaor .class to the command-line arguments. (If your CLASSPATHenvironment variable does not include the current directory, you will need to use a command line like
javah -classpath .;C:\Java\lib\classes.zip PortfolioEntry)
Now take a look at the output file PortfolioEntry.h. You should see something like List-ing 14.2.

Listing 14.2. First PortfolioEntry.hfile.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <native.h>
/* Header for class PortfolioEntry */

#ifndef _Included_PortfolioEntry
#define _Included_PortfolioEntry
struct Hjava_lang_String;

typedef struct ClassPortfolioEntry {
    struct Hjava_lang_String *TickerSymbol; 
    long NumberOfShares;
    float LastQuote;
    float BoughtAtPrice;
    float LastDividends;
    float LastEPS;
} ClassPortfolioEntry;
HandleTo(PortfolioEntry);

#ifdef __cplusplus
extern "C" {
#endif
extern void PortfolioEntry_FetchQuote(struct HPortfolioEntry *); 
extern float PortfolioEntry_CurrentValue(struct HPortfolioEntry *);
extern float PortfolioEntry_NetGain(struct HPortfolioEntry *); 
extern float PortfolioEntry_Yield(struct HPortfolioEntry *);
#ifdef __cplusplus
}
#endif
#endif

You can see how the class PortfolioEntrymaps directly to the structure ClassPortfolioEntry. The class methods map directly to the C function declarations.

Where Did HPortfolioEntry Come From?
The structure HPortfolioEntry, which is passed to each of the C functions, is declared as a result of the HandleTo() macro, which is defined in \java\include\oobj.h as
#define HandleTo(T) typedef struct H##T { Class##T *obj; \
                     struct methodtable *methods;} H##T
So, HandleTo(PortfolioEntry); expands to
typedef struct HPortfolioEntry {
   ClassPortfolioEntry *obj;
   struct methodtable *methods;
} HPortfolioEntry;
This structure provides the bookkeeping that will allow C functions to behave like class methods. To access an instance variable from your native method, follow the obj pointer to the instance variable structure declared by the header file. For example, one of the native methods for PortfolioEntry would access the NumberOfShares attribute by dereferencing hPortfolioEntry->obj->NumberOfShares.
The methods pointer also allows your native method to get information about, and even invoke, the other methods of your class. Until you are quite conversant with the Java object model, you should probably avoid this feature.
Now, create the stub file:
>javah -stubs -classpath .;C:\JAVA\LIB\CLASSES.ZIP PortfolioEntry
This creates the rather obfuscated file in Listing 14.3. You should never need to modify the stub file. In fact, it is best to ignore it completely after you have created it.

Listing 14.3. Stub file for PortfolioEntryclass.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <StubPreamble.h>

/* Stubs for class PortfolioEntry */
/* SYMBOL: "PortfolioEntry/FetchQuote()V",
           Java_PortfolioEntry_FetchQuote_stub */
__declspec(dllexport) stack_item *
  Java_PortfolioEntry_FetchQuote_stub(stack_item *_P_, 
                                      struct execenv *_EE_)
{
   extern void PortfolioEntry_FetchQuote(void *); 
   (void) PortfolioEntry_FetchQuote(_P_[0].p); 
   return _P_;
}
/* SYMBOL: "PortfolioEntry/CurrentValue()F",
           Java_PortfolioEntry_CurrentValue_stub */
__declspec(dllexport) stack_item *
  Java_PortfolioEntry_CurrentValue_stub(stack_item *_P_, 
                                        struct execenv *_EE_)
{
   extern float PortfolioEntry_CurrentValue(void *);
   _P_[0].f = PortfolioEntry_CurrentValue(_P_[0].p); 
   return _P_ + 1;
}
/* SYMBOL: "PortfolioEntry/NetGain()F",
           Java_PortfolioEntry_NetGain_stub */
__declspec(dllexport) stack_item *
  Java_PortfolioEntry_NetGain_stub(stack_item *_P_, 
                                   struct execenv *_EE_)
{
   extern float PortfolioEntry_NetGain(void *); 
   _P_[0].f = PortfolioEntry_NetGain(_P_[0].p); 
   return _P_ + 1;
}
/* SYMBOL: "PortfolioEntry/Yield()F",
           Java_PortfolioEntry_Yield_stub */
__declspec(dllexport) stack_item *
  Java_PortfolioEntry_Yield_stub(stack_item *_P_,
                                 struct execenv *_EE_)
{
   extern float PortfolioEntry_Yield(void *);
   _P_[0].f = PortfolioEntry_Yield(_P_[0].p);
   return _P_ + 1;
}

Looking back at the class definition, it seems that most of the methods could be implemented in Java alone. Suppose that the function to retrieve the latest quote used a third-party C library. Now modify the class definition to include some native and some Java methods, as shown in Listing 14.4.

Listing 14.4. Revised PortfolioEntryclass.
class PortfolioEntry {
   String  TickerSymbol;
   int        NumberOfShares; 
   float   LastQuote;
   float   BoughtAtPrice;
   float   LastDividends;
   float   LastEPS;

   public native void  FetchQuote(); 
   public float CurrentValue() {
      return (NumberOfShares * LastQuote); 
   };
   public float NetGain() {
      return (CurrentValue() - NumberOfShares * BoughtAtPrice);
   }
   public float Yield() {
      return (100 * (NetGain() / NumberOfShares * BoughtAtPrice));
   };
};

Now, recompile the class and regenerate the header file. Notice the change in the declarations section of the header file in Listing 14.5.

Listing 14.5. Header file for revised PortfolioEntryclass.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <native.h>
/* Header for class PortfolioEntry */

#ifndef _Included_PortfolioEntry
#define _Included_PortfolioEntry
struct Hjava_lang_String;

typedef struct ClassPortfolioEntry {
    struct Hjava_lang_String *TickerSymbol; 
    long NumberOfShares;
    float LastQuote;
    float BoughtAtPrice;
    float LastDividends;
    float LastEPS;
} ClassPortfolioEntry;
HandleTo(PortfolioEntry);

#ifdef __cplusplus
extern "C" {
#endif
extern void PortfolioEntry_FetchQuote(struct HPortfolioEntry *); 
#ifdef __cplusplus
}
#endif
#endif

Because only one method is declared as native this time, that is the only method mapped to a C function. This also implies that your C code will not be able to call any Java methods in the class, although the C functions can call each other freely. Keep in mind that the javah-generated header file only declares the C functions that the Java runtime system can call. In your implementation file, you can create as many C functions as you need. You are free to modularize your native methods as much as necessary. The C functions have access to the instance variables of the calling object. To encourage encapsulation, you should give any additional C functions you create file scope, by declaring them as static. That way, they cannot be called inadvertently by other C modules. Likewise, if you need to use any global variables in your implementation file, you should give them file scope, too. (Look carefully at any global variables in the implementation file. Most of the time, you will find that they should really be instance variables.)
One last thing you want to add to your Java class is a static code block to load the dynamically loadable library. Recall that the Java runtime executes a class's static block the first time that class is loaded. Think of it as an initializer for the class. Assume that the example's native methods are compiled and linked into a library called "PortfolioEntry." The static code block shown in Listing 14.6 loads the library when the class is loaded.

Listing 14.6. Final revision of the PortfolioEntryclass.
class PortfolioEntry {
   String TickerSymbol;
   int         NumberOfShares; 
   float   LastQuote;
   float   BoughtAtPrice;
   float   LastDividends;
   float   LastEPS;

   static {
      System.loadLibrary("PortfolioEntry"); 
   }
   public native void FetchQuote();
   public float CurrentValue() {
      return (NumberOfShares * LastQuote); 
   };
   public float NetGain() {
      return (CurrentValue() - NumberOfShares * BoughtAtPrice);
   }
   public float Yield() {
      return (100 * (NetGain() / NumberOfShares * BoughtAtPrice));
   };
};


(See java.lang.System for details about the System.loadLibrarymethod.) You should know that System.loadLibrarywill throw an exception if it cannot find the specified library. Because this exception will be thrown when the Java runtime loads the class, before your application starts, you will not be able to catch it. You do not need to explicitly unload the library-the Java runtime system will handle that for you. 

No comments:

Post a Comment