HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

The assignment was enough to make any neophyte Java developer bolt for the door: to provide a remote method for use by an applet that invokes a native method that wraps a function in an existing legacy library. Mentally calculating the odds of making it to the parking lot, I discarded that option and indicated my willingness to assume responsibility for the task with an air of cautious confidence. The purpose of the remote method is to return an instance of a class object whose contents reflect the data structure returned by the legacy function. Little did I know what I was getting myself into.

Perhaps the most significant hurdle I had to overcome was the lack of useful documentation to help direct my efforts. While embroiled in implementation, I spent an entire day poring through the RMI usergroup archive on Sun's Web site searching for guidance to no avail. I would've spent a lot of time wading through their JNI usergroup archive as well, but I couldn't seem to locate one. Subsequently I made the decision to document my findings so as to assist others.

Before we start on the class design, let's look at what the existing legacy code (Get_Legacy_Data) does. An ASCII file is read from the local disk, then its contents are parsed into a Legacy_Type structure whose address is passed as an argument by the caller (see Listing 1). Not much to it, really. The legacy code was compiled into a shared object library, legacy.so, using the IRIX 6.2 compiler and then loaded onto the Web server, a Silicon Graphics Indy station loaded with the IRIX 6.4 operating system.

The first requirement for class design is a class that acts as a template for the data structure that's returned by the legacy function. This class, JLegacy, declares a series of public instance variables that correspond to the members of Legacy_Type and provides a constructor that has no parameters (see Listing 2). This constructor is never called, not even by the native method that allocates the object for return to the remote method.

Next, the remote interface declaration for the remote object must be defined. The remote interface is a Java interface that extends java.rmi.Remote, used exclusively to identify remote objects. The remote method getJLegacy, which is defined by JLegacyIF, returns a JLegacy instance and throws java.rmi.RemoteException, which provides a mechanism to handle any failures (see Listing 3).

Now that the remote interface has been defined, let's look at the design of the remote object, JLegacyRO (see Listing 4). For JLegacyRO to implement getJLegacy, it must interface with the existing legacy code through a native method, getN. This method is declared in the JLegacyRO class but implemented in C, just like the legacy code. It returns a JLegacy instance and is declared static since its implementation is the same for all instances of the JLegacyRO class. It's implemented in a native shared-object library, libJLEG.so, which is loaded into the Java Virtual Machine at runtime using a static initializer in the JLegacyRO class. Static initializers are executed once by the JVM when the class is first loaded. If JLegacyRO doesn't load the native library, an UnsatisfiedLinkError exception is thrown when getN is called. Failure to load libJLEG.so is established only by catching one of the exceptions thrown by System.loadLibrary. The JVM qualifies the library name, assigns the prefix lib and appends the library extension .so for UNIX and .dll for Microsoft Windows.

JLegacyRO calls getN and returns the JLegacy object returned by it to implement the method defined by JLegacyIF. Nothing to it, right? Well, let's finish the JLegacyRO class before we call this one complete.

The JLegacyRO class exports itself by extending UnicastRemoteObject and calling the constructor of its superclass in its own constructor. In addition, UnicastRemoteObject redefines the equals, hashCode and toString methods inherited from java.lang.Object for remote objects.

The first thing the main method provided by the JLegacyRO class does is install RMISecurityManager to protect its resources from remote client stubs during transactions. The RMISecurityManager is the equivalent of the applet security manager for remote object applications. Next, the main method creates an instance of the JLegacyRO class and a remote object registry listening on a port number, which is declared static final. The JLegacyRO class is the only application that will use this registry. Finally, the main method binds the instance of the JLegacyRO class to a unique name in the remote object registry, making the object available to clients on other virtual machines. The name bound to the object is formed using the port number and the name of the remote object's host, which is passed to the application as a command line argument and the String "JLegacyRO".

Before delving into the details of the native method, let's look at the last class the client-side class that invokes the method on the remote object, JLegacyC (see Listing 5). JLegacyC provides a constructor without parameters, which is never intended to be called, and a static method, get, which looks up the remote object in the registry created by JLegacyRO. This static method also retrieves a reference to JLegacyIF through which the remote method, getJLegacy, is invoked. The get method returns the JLegacy object that was returned by the remote method invocation.

These three classes and the interface are all compiled into the same package. All classes, including the stub and skeleton created from the JLegacyRO class using the rmic compiler, are served from the Indy Web server. The environment settings are explained at the conclusion of this article. The native method is also relatively straightforward.

Before we can discuss the details, however, we must establish its C prototype. The C header file, which defines the prototype for the native method, is generated using the javah tool with the -jni option on the compiled JLegacyRO class (see Listing 6). Since the JLegacyRO class has been compiled into a package, the package name must be appended to the class name when javah is executed (e.g., javah -jni my.jlegacy.classes.JLegacyRO). The resulting header file will be prefixed with the package name (e.g., my_jlegacy_classes_JLegacyRO.h).

If you've read the Java Native Interface specification, you're already familiar with the method used by javah in composing native method names. If you haven't, I must warn you it's not pretty. A native method name has the following signature: Java_<mangled fully qualified class name>_<mangled method name>. The term mangled is actually used in the JNI specification. If the native method is an overloaded method, the name is further appended with __<mangled argument signature>. There's that word again. For further information on the JVM's type signatures, I recommend reading the JNI specification.

The JNI interface (or JNIEnv) pointer is always the first argument to a native method. The interface pointer points to a table of function pointers, each a JNI function. In standard C, all JNI functions are called via this pointer (e.g., (env)->FindClass(env,"java/lang/String") ). The JNIEnv structure is defined in C++ with inline functions that ultimately resolve to the same references as the standard C functions. Since the sole purpose of the JNIEnv pointer is to invoke the JNI functions, and because it has a well-defined syntax, I wrapped all the JNI functions so as to promote greater readability and easy maintenance.

The second argument to a native method varies depending on whether or not the method is declared static. If the method is nonstatic, the argument is type jobject and is a pointer to the Java object that invoked the method. If the method is declared static, the argument is type jclass and is a pointer to the Java class that declared the method (i.e., the remote object class JLegacyRO). Any arguments passed to the native method in its Java declaration follow the second argument in the function prototype. In this case the method is declared with no arguments.

Remember about getN being declared as returning an instance of the JLegacy class? This is the jobject returned by the function in the C prototype. Briefly, the native method will retrieve the required data using the existing legacy function, instantiate the jobject to be returned and populate it with the retrieved data (see Listing 7).

First the native method calls Get_Legacy_Data, passing it a pointer to the Legacy_Type structure to be populated. Then the fun begins. Using the JNI AllocObject function, the native method allocates an object of the JLegacy class. The jclass must be established first, using the JNI FindClass function, because the native method is declared static in the JLegacyRO class. This means that the jclass argument passed to it isn't the class to which an object is to be allocated. The FindClass function requires a fully qualified class name (i.e., my/jlegacy/classes/JLegacy).

The JLegacy object is an example of a local reference, meaning its scope is for the lifetime of the native method and it's automatically freed by the JVM upon return. All objects passed into or returned from native methods are local references. Global references remain visible until they're freed.

Once the JLegacy object is returned, the native method must establish the field IDs for the public instance (nonstatic) variables within the Java object in order to access the variables or fields. Fields are identified by the JNI, using their symbolic names and type signatures.

Finally, the instance fields are set to the contents of the Legacy_Type structure returned by Get_Legacy_Data using the JNI Set<type>Field family of accessor routines, and the populated JLegacy object is returned to the interface implemented by JLegacyRO. Former C programmers should note that the Set<type>Field routines are provided only for the following primitives: boolean, byte, char, short, int, long, float and double; everything else is an object of some sort.

In this case a series of the members in the Legacy_Type structure returned by Get_Legacy_Data are char arrays or UTF-8 format in Java. The UTF-8 format encodes nonnull ASCII characters in the range 0x01 to 0x7F (hexadecimal) in a single byte. Characters above 0x7F are encoded using up to 3 bytes of storage. The JNI SetObjectField function requires a native type for the value of the indicated field, so the char arrays must be converted to java.lang.String objects before their instance fields can be set in the Java object. This translation may be performed using the JNI NewStringUTF function. Since a series of these instance fields have to be set, the steps needed to do this are generalized into another function, JL_SetStringField.

If an error condition arises during execution of the native method, the method will delete the local reference that the JLegacy object pointed to and then return a null object to the interface implemented by JLegacyRO. Freeing the local reference is a habitual practice of mine when I write C code, though it's not required in Java; I just think it's good programming style.

Now let's make everything talk to each other. First let's discuss compiling getN into the native shared-object library, libJLEG.so. In the makefile for libJLEG.so, legacy.so must be supplied as an argument to the link editor in order to resolve the symbol supplied by Get_Legacy_Data's object module for getN. In addition, Java 3.1 (Sun 1.1.5) assumes the runtime linker to load n32 libraries. If you attempt to load an o32 native library from the JLegacyRO class, a fatal error will be returned by rld. It can't successfully map the shared object name to the LD_LIBRARY_PATH despite the presence of the native library located at a path specified by the environment variable.

To facilitate loading an o32 library, two options are available. The first is to set the environment variable SGI_ABI to "-32" before starting JLegacyRO. The second is to pass the "-32" argument to the Java interpreter when starting JLegacyRO. On the Indy Web server the LD_LIBRARY_PATH variable must include the path for libJLEG.so and legacy.so, as well as <yourJAVA_HOMEpath>/lib/sgi/green_threads.

Apparently the JVM for the Silicon Graphics platform uses the default Green threads package as its user threading model. The Green threads package maps all Java threads into a single native thread, prohibiting concurrent execution of multiple threads in a Java application. In addition, the CLASSPATH variable on the Indy Web server must include the path that precedes the directory structure, defined by the package the classes were compiled in, so the Java interpreter can locate them. Finally, the applet class is served from the Indy Web server by setting the CODEBASE attribute accordingly in the HTML file.

I hope this article answers more questions than it raises. I know I learned a lot while working on this task and even more while writing about it. I hope you did, too.

Although all of these classes were served from a single Indy Web server, a summary illustrating the client and server classes running on different platforms might be useful to make clear on which platform each class belongs and each command-line step takes place (see Table 1).

Table 1

In this context client refers to the process (i.e., applet) invoking a method defined by a remote object and server refers to the remote object process. The rmic compiler is used on the server platform to create the stub and skeleton classes; the stub class is copied to the client platform before runtime. In addition, javah is used on the server platform to generate the header file that defines the C prototype for the native method declared by the remote object class; development of the source file that implements the C function is left to the user. The make of the native shared-object library on the server platform isn't illustrated, nor is browser startup on the client platform.

About the Author
Scott Howard, a staff analyst for New Technology, Inc., in Huntsville, Alabama, has developed software for private industry and the aerospace community for 13 years using FORTRAN 77, C and now Java. He's also a contributor to the Enhanced Huntsville Operations Support Center System Web infrastructure and Java Common User Interface designs at Marshall Space Flight Center. He can be contacted at [email protected]


Listing 1: LEGACY.h 

 * LEGACY.h defines the legacy structures and the 
 * associated function prototypes 
typedef enum { 
} Legacy_P_Type 

typedef enum { 
} Legacy_M_Type; 

typedef struct { 
   time_t                   Timestamp; 
   Legacy_P_Type    P_Type; 
   unsigned char       Id; 
   Legacy_M_Type   M_Type; 
   char                      String_A[5]; 
   char                      String_B[5]; 
   char                      String_C[5]; 
   char                      String_D[5]; 
   char                      String_E[5]; 
   char                      String_F[5]; 
   char                      String_G[5]; 
   char                      String_H[5]; 
} Legacy_Type; 

int Get_Legacy_Data ( Legacy_Type *legacy ); 

Listing 2: JLegacy.java 

 * JLegacy.java provides a class for the legacy data. A 
 * populated instance of a JLegacy object is returned by 
 * JLegacyC.get(). 
package my.jlegacy.classes; 

public class JLegacy implements java.io.Serializable 
   public long     timestamp; 
   public int        pType; 
   public byte      id; 
   public int        mType; 
   public String   stringA; 
   public String   stringB; 
   public String   stringC; 
   public String   stringD; 
   public String   stringE; 
   public String   stringF; 
   public String   stringG; 
   public String   stringH; 

    public JLegacy() {} 

Listing 3: JLegacyIF.java 

 * JLegacyIF.java defines the method used to return a 
 * populated instance of a JLegacy object from a remote 
 * object. 
package my.jlegacy.classes; 

public interface JLegacyIF extends java.rmi.Remote 
   public JLegacy getJLegacy() 
             throws java.rmi.RemoteException; 

Listing 4: JLegacyRO.java 

 * JLegacyRO.java provides a remote object which returns a 
 * populated instance of a JLegacy object. 
package my.jlegacy.classes; 

import java.rmi.*; 
import java.rmi.registry.*; 
import java.rmi.server.*; 
import java.net.*; 
import java.io.*; 

public class JLegacyRO extends UnicastRemoteObject 
                                      implements JLegacyIF 
   // JLegacyRO listens on this port in remote object registry 
   public static final int RO_REGISTRY_PORT = 1099; 

   // The host address of JLegacyRO 
   private String host; 

   // Native method declaration 
   public static native JLegacy getN(); 

   // Static initializer 
      // Load the native library which includes getN 
      try { 
      catch (SecurityException    e) { e.printStackTrace(); } 
      catch (UnsatisfiedLinkError e) { e.printStackTrace(); } 

   public JLegacyRO() throws RemoteException { super();} 

   public JLegacy getJLegacy()throws RemoteException 
      JLegacy jleg = null; 

      jleg = JLegacyRO.getN(); 

      return jleg; 

   // Application 
   public static void main (String args[]) 
      JLegacyRO remote = null; 

         new RMISecurityManager()); 

         remote = new JLegacyRO(); 
      catch (RemoteException e) { e.printStackTrace(); } 

      if (remote != null) 
         if (args.length == 1) 
            // Get host address of remote object 
            remote.host = args[0]; 

            // Start registry and register remote object 
                  "JLegacyRO: creating registry"); 

               /* Create registry listening on 
                * RO_REGISTRY_PORT. We can do this since 
                * this application is the only one that's going to 
                * use this registry. 

               Naming.bind(  "rmi://" + remote.host + ":" 
                  + RO_REGISTRY_PORT 
                  + "/JLegacyRO", 

                  "JLegacyRO: bound in registry"); 
            catch (Exception e) { e.printStackTrace(); } 
               "usage: JLegacyRO host_address"); 
         }  // if (args.length == 1) 
      }  // if ( remote != null ) 
   }  // main 

Listing 5: JLegacyC.java 

 * JLegacyC.java provides a static method which returns a 
 * populated instance of a JLegacy object. 
package my.jlegacy.classes; 

import java.applet.*; 
import java.rmi.*; 

public class JLegacyC implements java.io.Serializable 
   public JLegacyC() {} 

   public static JLegacy get(Applet parent) 
      JLegacy   jleg  = null; 
      JLegacyIF ifc = null; 

         ifc = (JLegacyIF) 
         if (ifc != null) jleg  = ifc.getJLegacy(); 
      catch(Exception e) { e.printStackTrace(); } 

      return jleg; 

Listing 6: my_jlegacy_classes_JLegacyRO.h 

/* DO NOT EDIT THIS FILE - it is machine generated */ 
#include <jni.h> 
/* Header for class my_jlegacy_classes_JLegacyRO */ 

#ifndef _Included_my_jlegacy_classes_JLegacyRO 
#define _Included_my_jlegacy_classes_JLegacyRO 
#ifdef __cplusplus 
extern "C" { 
 * Class:     my_jlegacy_classes_JLegacyRO 
 * Method:    getN 
 * Signature: ()Lmy/jlegacy/classes/JLegacy; 
JNIEXPORT jobject JNICALL Java_my_jlegacy_classes_JLegacyRO_getN 
  (JNIEnv *, jclass); 

#ifdef __cplusplus 

Listing 7: Java_my_jlegacy_classes_JLegacyRO_getN.c 

 * Java_my_jlegacy_classes_JLegacyRO_getN.c contains the 
 * native function which retrieves the legacy data using the 
 * legacy library routine, instantiates a JLegacy object, and 
 * populates the object with the legacy data. 

#include <unistd.h> 
#include <jni.h> 

#include "my_jlegacy_classes_JLegacyRO.h" 
#include <LEGACY.h> 

/* The Java Native Interface functions used by this native 
 * method were wrapped to promote greater readability and 
 * ease of maintainability 
#define JNI_ALLOCOBJECT(class) 
   (*env)->AllocObject(env, (class)) 
   (*env)->DeleteLocalRef(env, (ref)) 
#define JNI_FINDCLASS(name) 
   (*env)->FindClass(env, (name)) 
#define JNI_GETFIELDID(name, sig) 
   (*env)->GetFieldID(env,jlClass, (name),(sig)) 
#define JNI_NEWSTRINGUTF(bytes) 
   (*env)->NewStringUTF(env, (bytes)) 
#define JNI_SETBYTEFIELD(id, val) 
   (*env)->SetByteField(env,jObject, (id),(val)) 
#define JNI_SETINTFIELD(id, val) 
   (*env)->SetIntField(env,jObject, (id), (val)) 
#define JNI_SETLONGFIELD(id, val) 
   (*env)->SetLongField(env,jObject, (id),(val)) 
#define JNI_SETOBJFIELD(id, val) 

/* Prototypes of functions found only in this file */ 
int JL_SetStringField(JNIEnv   *env, 
   jobject   jObject, 
   jfieldID   jFieldID, 
   const char *bytes); 

 * Class:     my_jlegacy_classes_JLegacyRO 
 * Method:    getN 
 * Signature: ()Lmy/jlegacy/classes/JLegacy; 

 * Java_my_jlegacy_classes_JLegacyRO_getN retrieves the 
 * legacy data using the legacy library routine, instantiates a 
 * JLegacy object, and populates the object with the legacy 
 * data.  Failure is indicated by returning a null object. 
      (JNIEnv *env, jclass jClass) 
Legacy_Type   legacy; 
   int                   istat                 = 0; 
   jclass               jlClass             = NULL; 
   jfieldID           timestamp_ID = NULL; 
   jfieldID           pType_ID   = NULL; 
   jfieldID           id_ID          = NULL; 
   jfieldID           mType_ID  = NULL; 
   jfieldID           stringA_ID = NULL; 
   jfieldID           stringB_ID = NULL; 
   jfieldID           stringC_ID = NULL; 
   jfieldID           stringD_ID = NULL; 
   jfieldID           stringE_ID = NULL; 
   jfieldID           stringF_ID = NULL; 
   jfieldID           stringG_ID = NULL; 
   jfieldID           stringH_ID = NULL; 
   jobject             jObject         = NULL; 

   istat = Get_Legacy_Data(&legacy); 
   if ( istat < 0 ) return NULL; 

   jlClass = 
   if ( jlClass ) 
      jObject = JNI_ALLOCOBJECT(jlClass); 
      if ( jObject ) 
         /* Establish the field IDs */ 
         timestamp_ID = 
            JNI_GETFIELDID("timestamp", "J"); 
         pType_ID       = JNI_GETFIELDID("pType", "I"); 
         id_ID              = JNI_GETFIELDID("id", "B"); 
         mType_ID      = JNI_GETFIELDID("mType", "I"); 
         stringA_ID     = 
         stringB_ID     = 
         stringC_ID     = 
         stringD_ID     = 
         stringE_ID      = 
         stringF_ID      = 
         stringG_ID     = 
         stringH_ID     = 

         /* Set the instance fields of the object to be returned */ 
         if (       timestamp_ID 
              && pType_ID 
              && id_ID 
              && mType_ID 
              && stringA_ID 
              && stringB_ID 
              && stringC_ID 
              && stringD_ID 
              && stringE_ID 
              && stringF_ID 
              && stringG_ID 
              && stringH_ID ) 
            JNI_SETINTFIELD(pType_ID, legacy.P_Type); 
            JNI_SETBYTEFIELD(id_ID, legacy.Id); 
            JNI_SETINTFIELD(mType_ID, legacy.M_Type); 

            if ( 
                  JL_SetStringField(env, jObject, stringA_ID, 
               && JL_SetStringField(env, jObject, stringB_ID, 
               && JL_SetStringField(env, jObject, stringC_ID, 
               && JL_SetStringField(env, jObject, stringD_ID, 
               && JL_SetStringField(env, jObject, stringE_ID, 
               && JL_SetStringField(env, jObject, stringF_ID, 
               && JL_SetStringField(env, jObject, stringG_ID, 
               && JL_SetStringField(env, jObject, stringH_ID, 
               /* Instance string fields of object have been set */ 
               return NULL; 
            return NULL; 
      } /* if ( jObject ) */ 
   } /* if ( jlClass ) */ 

   return jObject; 
} /* End of Java_my_jlegacy_classes_JLegacyRO_getN */ 

 * JL_SetStringField sets the indicated field of the object to 
 * a java.lang.String object constructed from the indicated 
 * char array. 
int JL_SetStringField(JNIEnv   *env, 
   jobject   jObject, 
   jfieldID   jFieldID, 
   const char *bytes) 
   int       retval = 1; 
   jstring jString; 

   jString = JNI_NEWSTRINGUTF(bytes); 
   if ( jString ) 
      JNI_SETOBJFIELD(jFieldID, jString); 
      retval = 0; 

   return retval; 
} /* End of JL_SetStringField */ 



All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.