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

Although we try to make our applications pure Java, outside forces sometimes make this impossible. We had such a case recently in our shop when we had to interface to an external device with an API that supported C language calls.

This is a typical case for the Java Native Interface (JNI). The JNI provides Java programs with a gateway to other languages and enables applications written in other languages to invoke the Java Virtual Machine.

This article focuses on the first of these two uses. Specifically, it discusses supporting a C/C++ API in Java to allow a Java application to use it. This is probably the situation in which the JNI is most frequently employed in production environments. Indeed, the seminal work on the JNI, The Java Native Interface: Programmer's Guide and Specification by Sheng Liang, devotes a chapter to this question. There's no theory involved, but if you use the following techniques you should save yourself many hours of work.

Phase one of the JNI programmer's acclimatization process: dump the sacred principle of Java development - platform independence. It must, as a purely logical implication, go out the window. All the C/C++ code in this article compiles under GNU C/C++ on Solaris and Microsoft Visual C++ on the Win32 platform. Other platforms would have to be tested individually.

I'll focus on the practical problem of implementing a large and complex API using the JNI within a production environment. Space constraints required Liang to discuss passing parameters that consisted of Java primitives from Java to C/C++ (hereafter C++, unless the distinction matters) and sending the C++ return code to the Java application. However, most APIs are more complex. This is something not frequently addressed in the literature, despite its prevalence in the real world. APIs are not restricted to primitives as parameters but pass structures and classes to the device. They not only use return statements but also assign values to the passed parameters for use by the calling program. They present questions for interpreting Java classes in C++, handling structure alignment and data format issues, and implementing code that is source compatible with both 32-bit and 64-bit processors.

The JNI, Power - at a Price
Every developer who has used the JNI is aware that it's powerful and comprehensive. However, the price of these features is an inordinate amount of work to accomplish some very mundane tasks. In particular, parameter handling is a time- and code-intensive business that's subject to errors and latent bugs. Consider the code in Listing 1 , which might be described as the simplest function to access a JNI. The native function is declared at (1) in the listing. (In Listings 1, 4, and 6 I've numbered specific lines for reference purposes.)

The method passes one integer to a C++ native function. The native function prints the passed parameter to stdout and also returns it. The native code is shown in Listing 2 . Note the obscure function name, which is generated by javah.

The native function just prints the integer parameter to stdout. How this appears depends on your Java development environment. In Borland JBuilder the output from the native code and the System.out.println()call both appear in the IDE message window, as shown below.

Value is 5
Value sent and returned is 5

The native code prints the first line and Java prints the second. The important points about this example are:

  • The integer parameter is passed from Java to C++ as a primitive, so the native code can access it directly.
  • The same rule applies to all other Java primitives.
  • The use of the JNI is straightforward, and the native code can be developed rapidly as long as its use is confined to passing primitives.
  • The native code may require modification for different platforms. While ISO standardization has made C++ source code fairly portable, we're out of the area of Java binary compatibility.

    Most data types are more complicated than primitives. In Listings 3   and 4 the Java and native code for passing and returning a string type are shown. The following is the output from these listings.

    Value is Hello World
    Value returned is Goodbye World

    While there's virtually no change in the Java code (essentially just a change of the operative variable type from int to string), the native code is substantially more involved. Because a string is neither a primitive type nor architecturally the same as any string in the native language, it must be handled differently. First, the JNI function GetStringUTFChars() [at (1) in Listing 4] both converts the string to the C++ single-byte character set and retrieves a pointer to the resultant object (other JNI functions can handle conversion to C++ wide, e.g., Unicode, strings). Second, the JNI function ReleaseStringUTF() frees the memory allocated for the converted string. Third, the JNI function NewStringUTF() allocates a Java string from a C++ single-byte character string to return a string to Java. The important point about Listing 3 is that passing strings increase the amount of native code necessary to process the parameter.

    String parameters don't present even the normal level of variable complexity. Real-world data types are likely to be represented as classes in Java and structures or classes in C++. Listings 5   and 6 provide the Java and C++ code for passing a simple class.

    The Java code in Listing 5 is very simple. It just instantiates instances of ClassAccess and Device2 where Device2 aggregates an instance of Device1, which it instantiates in the constructor chain. The code is straightforward, indeed mechanical. However, the native method (see Listing 6) is completely different. While not conceptually sophisticated, it's a complicated tangle of repetitive operations on different data types. First, at (1), the code fetches the class of the object passed to the native function. This is the Device2 object passed in the Java line classAccess.accessClass(dev2). It then proceeds to retrieve each parameter in turn. The block at (2) retrieves the int field intValue. The code at (3) retrieves the long field longValue. The block at (4) is more involved as it retrieves the string field stringValue and resembles Listing 3. Finally, the block that begins at (5) shows the more involved case of obtaining a field from an object contained within another object. In this example the Device2 object contains a Device1 object. It's the int member of Device1, intValue, that we wish to eventually obtain. [This is obtained at (6).]

    Simplifying the Implementation
    Code like this will be repeated unless we find a way to move its regularities into separate functions. The string and contained object cases generate the most overhead, so they're the ones chosen in Listing 7 . Now the native code in Listing 5 can be abbreviated to that shown in Listing 8 .

    Furthermore, the two access functions repeat the simplification that they provide here in each native function they're called in. In each instance that we use this code we reduce the amount of code required to be implemented in the project by roughly two-thirds.

    Assigning Data to Java Structures
    Now we're at the point at which productive use of the JNI became a major issue in our shop. The vendor API that we supported required some structure passing and what we discussed earlier proved useful for this. However, the API's main purpose was to return data from the vendor's device, which it did by providing over 100 functions that required a pointer to an API structure to be passed to an API function. The vendor library allocated memory for the structure and instantiated the fields with the requested data. This worked well for the C++ programmer who could examine the data by dereferencing the pointer after the API function call returned.

    The Java programmer acquired a new set of problems. We decided to define a Java class for each C++ structure (a practice recommended by Bloch). Each field of each API structure had to be assigned to a member of the corresponding Java class, passed as a parameter. The one-to-one mapping of Java classes to C++ structures gave us over 60 Java classes. The number of structure members meant that over 500 member assignments had to be made. Code resembling that in Listings 1-5 would have to be written and, more important, maintained - a daunting prospect.

    We turned to a set of functions like those in Listing 7. However, now these functions would read a member variable from a C++ structure and assign the value to the Java class (see Listing 9 for an example). For continuity, we continue to use the Java classes Device1 and Device2 defined earlier. Their C++ equivalents are:

    struct Device1
    {
    int intValue;
    };


    struct Device3
    {
    int intValue;
    long longValue;
    char * stringValue;
    Device1 dev1Value;
    };

    Assume that at runtime they contain the values shown below:

    struct Device1 dev1 = {10};

    struct Device3 dev3 = {
    5,
    8,
    "Hello World",
    {10}
    };

    Listing 9 is the Java program that will print out these values.

    The C++ code (see Listing 10) shows the implementation of the native method assignClass. One interesting feature is that it shows how to make assignments to nested objects (Device1 is aggregated by Device3 in Listing 9). Just as in the case of passing parameters from Java to C++ we provide a set of routines to expedite the process and make the code more reliable (see Listing 10). Listing 11 can then be replaced with Listing 12. (Listings 11 and 12 can be downloaded from below.)

    Benefits
    The family of Parse... functions has substantially reduced the amount of code required to make the necessary assignments to the Java object passed as jOutput and its contained objects. Furthermore, we've moved the intricate code in which errors can occur into a set of reusable functions. This expedites the implementation of a large vendor API in Java and makes maintenance more straightforward. These functions are extensible to other variable types as necessary. We've implemented only the ones that we need.

    Summary
    The JNI is a powerful, comprehensive solution to the problem of accessing an API written to support other languages, especially C or C++. That power comes at the price of intricate parameter manipulations. In this article we addressed the problem of using the JNI in a production situation and presented a set of techniques that simplify most of the repetitive tasks of passing information to the native code and getting it back into our Java program. These techniques reduced by around two-thirds the amount of C++ code that had to be written. These techniques can be extended to cover other constructs, and it's a pragmatic choice as to which should be implemented in a particular project.

    Postscript
    After completing the project in which these techniques were developed, another JNI project came up. Implementation of the native parts of the project took about a quarter of the time that we had forecast, based on raw coding of JNI function calls. The code also passed all unit tests on Solaris and Windows the first time, an indication of the reliability gains of a unified method of handling parameter passing. While an unscientific estimate (different APIs, etc.), the difference in terms of schedule improvements was very noticeable.

    References

  • Liang, S. (1999). The Java Native Interface: Programmer's Guide and Specification. Addison-Wesley.
  • Bloch, J. (2001). Effective Java Programming Language Guide. Addison-Wesley.

    Author Bio
    Andrew J Chalk, is president of Magna Carta Software, Inc., in Plano, Texas. [email protected]

    	
    
    
    Listing 1: Passing an int to the JNI
    
    package jni_article_;
    
    public class IntegerAccess {
      static {
        System.loadLibrary("EasyJNI");
      }
      private native int accessInt(int value);
      // (1)
      private int value;
      public IntegerAccess() {value = 5;}
    
      public static void main(String[] args) {
       IntegerAccess integerAccess1 = new IntegerAccess();
    System.out.println("Value sent and returned is " +
      integerAccess1.accessInt(integerAccess1.value));
      }
    }  
    
    
    Listing 2: Native code called in Listing 1
    
    // Headers omitted...
    JNIEXPORT jint JNICALL Java_jni_1article_1_IntegerAccess_accessInt
      (JNIEnv *, jobject, jint jInt)
    {
      cout << "Value is " << jInt << endl;
    	
      return (jInt);
    }
    
    
    Listing 3: Passing a string parameter from Java to C++ 
    
    package jni_article_;
    
    public class StringAccess {
      static {
        System.loadLibrary("EasyJNI");
      }
      private native String accessString(String stringValue);
      public StringAccess() {
      }
    
      public static void main(String[] args) {
        StringAccess stringAccess1 = new StringAccess();
        System.out.println("Value sent and returned is " +
          stringAccess1.accessString("Hello World"));
      }
    }
    
    
    Listing 4: Native code called in Listing 3
    
    // Headers omitted...
    JNIEXPORT jstring JNICALL Java_jni_1article_1_StringAccess_accessString
      (JNIEnv *env, jobject jo, jstring jStringValue)
    {
      jstring jStr = 0;
      const char * str = 
        env->GetStringUTFChars(jStringValue, NULL);
    	// (1)
      if (str) {
        cout << "Value is " << str << endl;
        jStr = env->NewStringUTF("Goodbye World");
        env->ReleaseStringUTFChars(jStringValue, str);
      }
    
      return (jStr);
    }
    
    
    Listing 5: Passing a class parameter from Java to C++.
    
    package jni_article_;
    
    public class ClassAccess {
      static {
        System.loadLibrary("EasyJNI");
      }
      private native int accessClass(Device1 devName);
      private native int accessClass(Device2 devName);
      public ClassAccess() {
      }
    
      public static void main(String[] args) {
        ClassAccess classAccess = new ClassAccess();
        Device2 dev2 = new Device2();
        classAccess.accessClass(dev2);
      }
    }
    
    
    // In device1.java
    package jni_article_;
    
    public class Device1 {
      public Device1 (int value) {
        intValue = value;
      }
      int intValue;
    }
    
    // In device2.java
    package jni_article_;
    
    public class Device2 {
      public Device2 () {
        intValue = 1;
        longValue = 11;
        stringValue = new String("Hello World");
        dev1Value = new Device1(5);
      }
      private int intValue;
      private long longValue;
    
    
      String stringValue;
      Device1 dev1Value;
    }
    
    
    Listing 6: Native code called in Listing 5
    
    // Headers omitted...
    // ClassAssign.AccessClass(Device2)
    JNIEXPORT jint JNICALLJava_jni_1article_1_
    ClassAccess_accessClass__Ljni_1article_1_Device2_2
      (JNIEnv *env, jobject jo, jobject jOutput)
    {
    // get the class of the object parameter
    	jclass cls0 = env->GetObjectClass(jOutput);
    // (1)
    	               if (cls0) {
    // get the field ID of the "intValue" field of this class
    	jfieldID fidInt = env->GetFieldID(cls0, "intValue", "I");
    // (2)
    	               if (fidInt) {
    // get the value of the field in this instance of the above class
    	jint valInt = env->GetIntField(jOutput, fidInt);
    	cout << "Value is " << valInt << endl;
    
    // get the field ID of the "longValue" field of this class
    	jfieldID fidLong = env->GetFieldID(cls0, "longValue", "J");
    // (3)
    	               if (fidLong) {
    // get the value of the field in this instance of the above class
    	jlong valLong = env->GetLongField(jOutput, fidLong);
    	cout << "Value is " << (long) valLong << endl;
    				
    // get the field ID of the String field of this class
    	jfieldID fidString = env->GetFieldID(cls0, 
    	"stringValue", "Ljava/lang/String;");
    // (4)
    	                if (fidString) {
    // get the value of the field in this instance of the above class
       jstring valString = (jstring) env->GetObjectField(jOutput, fidString);
    	                if (valString) {
    const char * str = env->GetStringUTFChars(valString, NULL);
    	                if (str) {
    cout << "Value is " << str << endl;
    env->ReleaseStringUTFChars(valString, str);
    
    // get the field ID of the Device1 object field of this class
        jfieldID fidObject = env->GetFieldID(cls0, 
    	"dev1Value", "Ljni_article_/Device1;");
    // (5)
    	            if (fidObject) {
    // get a reference to this field in this instance of the class
    	jobject valObject = env->GetObjectField(jOutput, fidObject);
    	           if (valObject) {
    // get the class of the object parameter
    	jclass clsObject = env->GetObjectClass(valObject);
    	           if (clsObject) {
    // get the field ID of the "intValue" field of this class
    	jfieldID fidInt = env->GetFieldID(clsObject, "intValue", "I");
    		       if (fidInt) {
    // get the value of the field in this instance of the class
    	jint valInt = env->GetIntField(valObject, fidInt);
    // (6)
    	cout << "Value is " << valInt << endl;
                         }
                       }
                     }
                   }
                 }
               }
             }
           }
         }
       }
    
    	return (0);
    }
    
    
    Listing 7: Functions to handle fetching string and object members of a Java
     
    
    // class
    void AccessString(JNIEnv *env, jclass clsContaining, 
    jobject jContaining, LPCTSTR lpszFieldName, string & strOut)
    {
    	jfieldID fidString = env->GetFieldID(clsContaining, 
    	lpszFieldName, "Ljava/lang/String;");	
    // get the field ID of the field of this class
    	             if (fidString) {
    	jstring valString = (jstring) env->GetObjectField(jContaining, fidString);
    // get the value of the field in this instance of the above class
    		         if (valString) {
    	const char * str = env->GetStringUTFChars(valString, NULL);
    			     if (str) {
    	strOut = str;
    	env->ReleaseStringUTFChars(valString, str);
             }
           }
         }
       }
    
    void AccessObject(JNIEnv *env, jclass clsContaining, 
    jobject jContaining, LPCTSTR lpszFieldName, LPCTSTR lpszFieldSig, 
    class *clsContained, jobject *oContained)
    {
    // Code defensively. Initialize with "failure" values for safety
    	*clsContained = static_cast<jclass>(*oContained = 0);
    // get the field ID of the field of this class
    	jfieldID fidObject = env->GetFieldID(clsContaining, lpszFieldName, lpszFieldSig);
    	             if (fidObject) {
    // get the value of the object field in this instance of the above class
    	*oContained = env->GetObjectField(jContaining, fidObject);
    		         if (*oContained) {
    // get the class of the object parameter
    	*clsContained = env->GetObjectClass(*oContained);
         }
       }
    
    	return;
    }	
    
    
    Listing 8: Listing 5 modified to use the access...
    
    // functions
    JNIEXPORT jint JNICALL Java_jni_1article_1_ClassAccess_
    accessClass__Ljni_1article_1_Device2_2
      (JNIEnv *env, jobject jo, jobject jOutput)
    {
    // get the class of the object parameter
      jclass cls0 = env->GetObjectClass(jOutput);	
      if (cls0) {
    // int and long code here...(omitted for clarity)
    string sOut;
    AccessString(env, cls0, jOutput, "stringValue", sOut);
    cout << "Value is " << sOut.c_str() << endl;
    	
        jclass clsContained;
        jobject oContained;
        AccessObject(env, cls0, jOutput, "dev1Value",
       "Ljni_article_/Device1;", &clsContained, &oContained);
                    if (clsContained && oContained) {
    // get the field ID of the intValue field of this
    // class
     jfieldID fidInt = env->GetFieldID(clsContained, "intValue", "I");
                    if (fidInt) {
    // get the value of the int field in this instance
    // of the above class
    	jint valInt = env->GetIntField(oContained, fidInt);
    	cout << "Value is " << valInt << endl;
           }
         }
       }
    
      return (0);
    }
    
    
    Listing 9: Assigning C structures to Java classes
    
    package jni_article_;
    
    public class ClassAssign {
      static {
        System.loadLibrary("EasyJNI");
      }
      private native int assignClass(Device3 devName);
      public ClassAssign() {
      }
    
      public static void main(String[] args) {
        ClassAssign classAssign = new ClassAssign();
        Device3 dev3 = new Device3();
        classAssign.assignClass(dev3);
        System.out.println("Integer value is "+ dev3.intValue);
        System.out.println("Long value is " + dev3.longValue);
        System.out.println("String value is " +
          dev3.stringValue);
        System.out.println("Integer value in Device 1 is " +
          dev3.dev1Value.intValue);
         }
       }
    
    
    Listing 10: Functions to handle assignment of C++ data types to Java fields
    
    
    // Get an object reference to a Java object type contained within a containing
    // Java object.
    jobject GetObjectReference(JNIEnv *env, 
    jclass clsStruct, jobject joStruct, 
    LPCSTR lpszJavaFieldName, LPCSTR lpszSignature)
    {
    // Get the field ID of the field in the Java object
    	jfieldID fidField = env->GetFieldID(clsStruct, lpszJavaFieldName, lpszSignature);
    	          if (fidField) {
    // get a reference to the SymAPIDirector object
    	jobject joField = env->GetObjectField(joStruct, fidField);
    		return (joField);
    	}
    	return (0);
    }
    
    /*
    Assign a C int value to a Java int field.
    The Java class is cls0. 
    The Java object is oOutput. 
    The C int value is uiMember.
    The Java class member name is lpszJavaFieldName.
    Usage:
    Return value:
    	1 => success;
    	0 => failure;
    */
    int ParseIntField(JNIEnv *env, jclass cls0, 
    jobject oOutput, unsigned uiMember, LPCSTR lpszJavaFieldName)
    {
    	int iRc = 0; // failure
    
    // Get the field ID of the field in the Java object.
    // Note the "I" JNI signature for an int.
    	jfieldID fid0 = env->GetFieldID(cls0, lpszJavaFieldName, "I");
    	         if (fid0) {
    // assign the (C++) int field to the Java object
    	env->SetIntField(oOutput, fid0, uiMember);
    		iRc = 1; // success
    	}
    
    	return (iRc);
    }
    
    /*
    Assign a C long value to a Java long field. 
    The Java class is cls0. 
    The Java object is oOutput. 
    The C long value is lCMember.
    The Java class member name is lpszJavaFieldName.
    Return value:
    	1 => success;
    	0 => failure;
    */
    int ParseLongField(JNIEnv *env, jclass cls0,
    jobject oOutput, long lCMember, LPCSTR lpszJavaFieldName)
    {
    	int iRc = 0; // failure
    
    // get the field ID of the field in the Java object
    	jfieldID fid0 = env->GetFieldID(cls0, lpszJavaFieldName, "J");
    	         if (fid0) {
    // assign the (C++) long field to the Java object
    	env->SetLongField(oOutput, fid0, lCMember);
    		iRc = 1; // success
    	}
    
    	return (iRc);
    }
    
    /*
    Assign a C single byte string value to a Java object field.
    The Java class is cls0. 
    The Java object is oOutput. 
    The C string is lpszCMember.
    The Java class member name is lpszJavaFieldName 
    and it's signature is lpszSignature.
    Return value:
    	1 => success;
    	0 => failure;
    */
    int ParseStringField(JNIEnv *env, jclass cls0,
    jobject oOutput, LPCSTR lpszCMember, LPCSTR lpszJavaFieldName)
    {
    	int iRc = 0; // failure
    // convert C-string member to a java String object
    	jstring str0 = env->NewStringUTF(lpszCMember);
    	          if (str0) {
    // get the field ID of the String field in the Java object
    	jfieldID fid0 = env->GetFieldID(cls0, lpszJavaFieldName,"Ljava/lang/String;");
    		      if (fid0) {
    // assign the (C++) string field to the Java object
    	env->SetObjectField(oOutput, fid0, str0);
    		iRc = 1; 
    // success
         }
       }
    
    	return (iRc);
    	
    	
    
    Additional Source Code Listings
    
    
    Listing 11: Native code called in Listing 9
    
    // Headers omitted…
    JNIEXPORT jint JNICALL Java_jni_1article_1_ClassAssign_assignClass
      (JNIEnv *env, jobject jo, jobject jOutput)
    {
    	int iRc = 0;
    
    // get the class of the object parameter
    	jclass cls0 = env->GetObjectClass(jOutput);
    // (1)
    	            if (cls0) {
    // get the field ID of the "intValue" field of this class
    	jfieldID fidInt = env->GetFieldID(cls0, "intValue", "I");
    // (2)
    	        	if (fidInt) {
    // assign the (C++) int member to the Java object
    	env->SetIntField(jOutput, fidInt, dev3.intValue);
    
    // get the field ID of the "longValue" field of this class
    	jfieldID fidLong = env->GetFieldID(cls0, "longValue", "J");
    // (3)
        			if (fidLong) {
    // assign the (C++) long member to the Java object
    	env->SetLongField(jOutput, fidLong, dev3.longValue);
    
    // convert C-string member to a java String object
    	jstring str0 = env->NewStringUTF(dev3.stringValue);
    // (4)
    				if (str0) {
    // get the field ID of the String field in the Java object
    	jfieldID fidString = env->GetFieldID(cls0, "stringValue", "Ljava/lang/String;"); 
    				if (fidString) {
    // assign the (C++) string field to the Java object
    	env->SetObjectField(jOutput, fidString, str0);
    
    // get the field ID of the object field in the Java object
    	jfieldID fidObject = env->GetFieldID(cls0, "dev1Value", "Ljni_article_/Device1;");
    // (5)
    				if (fidObject) {
    // get a reference to the object field in this instance of the class
    	jobject joDevice1 = env->GetObjectField(jOutput, fidObject);
    				if (joDevice1) {
    // get the class of the object parameter
    	jclass cls0 = env->GetObjectClass(joDevice1);
    				if (cls0) {
    // get the field ID of the "intValue" field of this class
    	jfieldID fidInt = env->GetFieldID(cls0, "intValue", "I");
    				if (fidInt) {
    // assign the (C++) int member to the Java object
    	env->SetIntField(joDevice1, fidInt, dev3.dev1Value.intValue);
    		iRc = 1; // success
                         }
                       }
                     }
                   }
                 }
              }
            }
          }
        }
      }
    
    	return (iRc);
    
    Listing 12: Listing 11 modified to use the Parse… functions defined in Listing 10 
    
    JNIEXPORT jint JNICALL Java_jni_1article_1_ClassAssign_assignClass
      (JNIEnv *env, jobject jo, jobject jOutput)
    {
      int iRc = 0;
    
      // get the class of the object parameter
      jclass cls0 = env->GetObjectClass(jOutput);	
      if (cls0) {
        iRc = ParseIntField(env, cls0, jOutput, dev3.intValue,
                "intValue");
        if (iRc) {
          iRc = ParseLongField(env, cls0, jOutput,
                  dev3.longValue, "longValue");
          if (iRc) {
            iRc = ParseStringField(env, cls0, jOutput,
                    dev3.stringValue, "stringValue");
            if (iRc) {
              jobject joDevice1 = GetObjectReference(env, cls0,
                                    jOutput, "dev1Value", 
                                    "Ljni_article_/Device1;");
              if (joDevice1) {
                // get the class of the object parameter
                jclass cls0 = env->GetObjectClass(joDevice1);
                if (cls0) {
                  iRc = ParseIntField(env, cls0, joDevice1,
                          dev3.dev1Value.intValue,
                          "intValue");
                  iRc = 1; // success
                 }
               }
             }
           }
         }
       }
    
      return (iRc);
    }
    
     
    

    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.