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

Most Java application developers would like to create pure Java implementations that provide solutions for their users. In practice, however, the fact that most hardware products are shipped with legacy software negates that lofty purity goal. With Java you can design your cross-platform GUI while taking advantage of JNI to create a wrapper layer around the code that can't be ported. For example, a requirement can be to create a Java application that provides support for a PCI Artificial Neural Network (ANN) card under OS/2 and Windows NT. The set of APIs that communicates with the Neural Network is written in C and wrapped in a JNI dynamic-link library (DLL). To control the Neural Network the Java application has to invoke the correct set of JNI ANN APIs.

Architecture of a Hardware Neural Network
A PCI ZISC card contains one onboard zero instruction set computer (ZISC) and as many as three single inline ZISC modules (SIZMs). Each SIZM has six ZISCs. You can also have a setup made up of several PCI ZISC slots using a mother-daughter arrangement. In addition, ISA cards are available. The ZISC, a digital ANN containing 36 neurons (see Figure 1), implements the restricted coulomb energy (RCE) and the K-nearest neighbor (KNN) algorithms. The neurons on each of the ZISC chips are arranged following a radial basis function (RBF)-like network topology (see Figure 2) composed of three layers. Each input node corresponds to a component (Vi) of a feature vector. The outputs are categories or solutions to a classification problem, and the connections between the second and third layers are established dynamically as a result of the learning process.
Figure 1
Figure 1:
Figure 2
Figure 2:

The ZISC C library DLL contains a rich set of APIs to do initialization, recognition, classification, learning, data extraction, neuron selection and saving/restoring ZISC registers.

Java vs C Data Types
Some C/C++ data types are not compatible with data types in Java. One of the first things to do when you get ready to create your Java wrapper class is to identify the native data types that will be matched on the Java side. Developers need to consider several factors, such as the performance of the resulting code and the desire to remain faithful to the structure of the native APIs being replaced (see Table 1 for the mapping used within the ZISC wrapper class).

Table 1

Developer Beware!
Determine your mapping as best you can to fit the situations in which the variables will be needed. Ask yourself questions: Is the native code using the variable and passing it to a function by value or reference? Is any one of the types being used as an array, and if so will your mapping cause the array contents to be lost? Don't be afraid to spend time working on this aspect of the design - ...you have to understand the interfaces of the functions you're wrapping.

JNI: A Quick Overview
Java can run on multiple platforms because the Java Virtual Machine (JVM) can interact with the underlying operating system software. As a Java developer, you'll soon need to invoke some native code if you haven't already. However, to create Java applications/applets that can be written once and tested everywhere, you have to encapsulate the native code interfaces and document them in such a way that anyone can understand where the impure code is located. Don't forget: this native code isn't portable.

JNI acts as the glue between your Java application and the supporting native code. It lets you invoke native functions, pass parameters to them and capture the return codes. To create a JNI application:

  1. Build a Java wrapper class that includes all the native functions you'll be calling from Java.
  2. Use the javah tool included in the Java JDK to create a C/C++ language header file that contains the Java to C/C++ prototypes.

    Use javah -help for more information on how it can be used. To build the C/C++ header file for Java wrapper class XXXX you'd execute javah -jni XXXX. jni_md.h contains all JNI-accepted calling sequences, such as JNIEXPORT and JNICALL.

    Native methods receive at least two of these parameters:

    • JNIEnv - points to the existing JNI environment (interface pointer) during method invocation, as well as to a table that contains the function pointers for all JNI conversion methods. This pointer should be used only within the thread where it was loaded by the JVM.
    • jobject - for the class instantiated object method or jclass for static methods.
    See file jni.h for all supported JNI types and data access methods. Also, don't be alarmed by the name mangling caused by javah when creating the native language prototypes, since they are╩invisible to the user. Notice how for the ZISC code a "_" is replaced by a "_1".
  3. Create a new C/C++ DLL project using your favorite development platform. This project should point to your generated header file. Notice the functions in jni.h are referenced differently depending on the type of source module used to build the native DLL. From a C source file we'd invoke a JNI conversion method as:
(*env)->GetByteArrayElements(env, componentArray, NULL);

From a C++ file the same function is referenced as:

env->GetByteArrayElements(componentArray, NULL);

Other conversion methods handle:

  • Obtaining the handle of the Java object's class
    j_class = (*env)->GetObjectClass(env, jobject_parameter);

  • Getting the value of an integer variable from Java. Use GetStaticFieldID and GetStaticIntField for static variables (see GetXXXXField for other types):
    j_fieldID = (*env)->GetFieldID(env, j_class, "int_variable_name", "I"); int_value = (*env)->GetIntField(env, jobject_parameter, j_fieldID);

  • Calling a Java method. Use GetStaticMethodID and CallStaticVoidMethod for static variables (see CallXXXXMethod for other types of methods).
    j_methodID = (*env)->GetMethodID(j_class, "void_method_name", "()V");
    (*env)->CallVoidMethod(j_class, j_methodID);
I seldom use these conversion methods as my designs specify that the hardware should know nothing about how the Java software is implemented. One of the few exceptions could be the JNI exception-handling conversion methods. The following sections describe how the ANN hardware provides services for the Java software making the requests. As a rule, avoid developing JNI code that "knows" the interfaces for the service provider layer and service requester layer. This results in an interface that will be hard to maintain and upgrade.

Neural Network Java Wrapper
The Java wrapper class JZISC was built using IBM VisualAge Java 2.0; the JNI library JZISC.DLL was built using Microsoft Visual C++ 5.0.
To create the JNI JZISC class, you can follow the steps listed in the previous section:

  1. Build a Java wrapper class JZISC that includes all functions you will be calling from Java. An example of a native Java method looks like:

    public native void JZ_Identify(byte compArray[], int nComp, int status[], int category[]);

    Notice the native keyword and the lack of a method body. Also, when the JZISC class is initialized it'll have to load the native DLL that contains the actual code.

    static { System.loadLibrary("JZISC"); // Make sure the file's directory is in PATH statement }

  2. Since the ANN Java wrapper class is called JZISC (see Figure 3), to build the C header file you would execute javah -jni JZISC. The corresponding generated prototype for the previously shown Java native method looks like:

    JNIEXPORT void JNICALL Java_JZISC_JZ_1Identify (JNIEnv *, jobject, jbyteArray, jint, jintArray, jintArray);

    Figure 3
    Figure 3:

  3. Your source file within the C DLL project should include all required header files like:

    #include /* JNI includes */ #include "JZISC.h" /* javah generated header file */ #include "zapi.h" /* ANN C prototypes for ZISC card */

When creating your function definitions, follow the format of the prototypes in the header file. For our sample method the actual implementation would look like the contents of Listing 1.

A Solution to the XOR Problem
In 1969, Minsky and Pappert demonstrated the limitations of a single-layer neural network by proving that it couldn't do a simple pattern classification task. If a multilayer ANN can solve the exclusive OR (XOR) problem, f(0 0)=0, f( 0 1)=1, f(1 0)=1, f(1 1)=0 (see Figure 4), then it can also classify more complicated input patterns that are not linearly separable.

Figure 4
Figure 4:

The first thing the Java sample application does is to initialize the ANN PCI card, query it for the number of neurons available, the number of cards installed - ISA or PCI - and the version number (see Listing 2). Next we load the XOR value pairs and the predicted results (see Listing 3 for an example of a pair of inputs and expected output). Note that the ANN expects to see inputs of type byte, and zero isn't a good choice for an input value since it doesn't provide enough resolution. With ANN implementations the developer has to normalize the input values. For this implementation we equate zero (false) to a value of 1 and 1 (true) to a value of 5. Once the training process has been completed, the network is tested by specifying an input pair and asking the network to return the predicted value (see Listing 4 for a portion of the output generated by the Java program).

This card can perform other tasks, such as executing other types of recognition schemes, minimizing the number of neurons used and saving the ANN weights, but that's another article.

References and Resources

  1. R. David, E. Williams, G. de Tremiolles and P.l Tannhof. "Noise Reduction and Image Enhancement Using a Hardware Implementation of Artificial Neural Networks." www.fr.ibm.com/france/cdlab/contact.htm
  2. IBM France ZISC home page: www.fr.ibm.com/france/cdlab/zisc.htm
  3. Silicon Recognition: www.silirec.com/
  4. S. Haykin (1994). Neural Networks: A Comprehensive Foundation, pp. 236-284. IEEE Press. ISBN 0-02-352761-7.
About the Author
Bernie Arruza is an advisory engineer at the IBM Manufacturing Technology Center in Boca Raton, Florida, with an MS in electrical engineering. His department designs and develops atomic force microscopes for the semiconductor industry. You can contact Bernie with questions and comments at [email protected]


Listing 1: JNI prototype implementation.
/* This is the actual C ZISC function prototype */ 
// void IMPORT Z_Identify(BYTE _far *componentArray, WORD _far NComp, WORD _far *Status,
 WORD _far *Category); 

/* This is a local wrapper for the actual C function. It can be used for debugging purposes
   verify data type conversions and returned values before they are passed to the third
   DLL */ 
void J_Z_Identify(BYTE *componentArray, WORD nComp, 
WORD *statusArray, WORD *categoryArray)

 Z_Identify(componentArray, nComp, statusArray, categoryArray); 

/* Implementation of the javah generated prototype */ 
Java_JZISC_JZ_1Identify(JNIEnv *env, jobject obj, 
jbyteArray componentArray, jint nComp,
 jintArray statusArray, jintArray categoryArray) { 
  // Get all component array elements 
 jbyte *component_array = 
  (*env)->GetByteArrayElements(env, componentArray, NULL); 

 // Get components from status array 
 jint *status_array = 
  (*env)->GetIntArrayElements(env, statusArray, NULL); 

 // Get components from category array 
 jint *category_array = 
  (*env)->GetIntArrayElements(env, categoryArray, NULL); 

  // Call local wrapper for ZISC method 
 J_Z_Identify((BYTE *)component_array, (WORD)nComp, 
 (WORD *)status_array, (WORD   *)category_array); 

  // Release work buffer for component array 
 (*env)->ReleaseByteArrayElements(env, componentArray, component_array, 0); 

  // Update status array with new value 
 (*env)->SetIntArrayRegion(env, statusArray, 0, 1, status_array); 

  // Release work buffer for status array 
 (*env)->ReleaseIntArrayElements(env, statusArray, status_array, 0); 

  // Update category array with new value 
 (*env)->SetIntArrayRegion(env, categoryArray, 0, 1, category_array); 

  // Release work buffer for category array 
 (*env)->ReleaseIntArrayElements(env, categoryArray, category_array, 0); 

Listing 2: Extract information from ANN hardware.
        // Create instance of JFC dialog 
  NeuralNet panel = new NeuralNet(); 

        // Create instance of Native class 
  JZISC ziscCARD = new JZISC(); 

        // Initialize ANN card 
  int initialized = (int)ziscCARD.JZ_Init(0); 

  if (1 == initialized) 
   System.out.println("cannot open ZISC device (type any key to exit)\n"); 

        // Query number of neurons in PCI board 
  int numberOfNeurons = ziscCARD.JZ_GetMaxNeurons(); 

       System.out.println("The ZISC has " + numberOfNeurons + "neurons\n"); 

        // Get ANN version and type 
  ziscCARD.info= ziscCARD.JZ_GetCardInfo(0); 

  System.out.println("ZISC Info " + ziscCARD.info + "\n"); 

Listing 3: ANN learning procedure.
  /********************** learning *******************************/ 
  /* Symbol 0 is represented by 1 and symbol 1 is represented by a 5 

  /********* input a new set of 2 components (0 0) ***/ 
  ziscCARD.array[0]= (byte)1; 
  ziscCARD.array[1]= (byte)1; 
  System.out.println("Vector (0 0) \n"); 
  ziscCARD.JZ_PutVector(ziscCARD.array, 2);    /* input components */ 
  ziscCARD.JZ_PutCat(1);              /* learn category 0 */ 

  /********* input a new set of 2 components (0 1) ***/ 
  ziscCARD.array[0]= (byte)1; 
  ziscCARD.array[1]= (byte)5; 
  System.out.println("Vector (0 1) \n"); 
  ziscCARD.JZ_PutVector(ziscCARD.array, 2);    /* input components */ 
  ziscCARD.JZ_PutCat(5);              /* learn category 1 */ 

  /********* input a new set of 2 components (1 0) ***/ 
  ziscCARD.array[0]= (byte)5; 
  ziscCARD.array[1]= (byte)1; 
  System.out.println("Vector (1 0) \n"); 
  ziscCARD.JZ_PutVector(ziscCARD.array, 2);    /* input components */ 
  ziscCARD.JZ_PutCat(5);              /* learn category 1 */ 

  /********* input a new set of 2 components (1 1) ***/ 
  ziscCARD.array[0]= (byte)5; 
  ziscCARD.array[1]= (byte)5; 
  System.out.println("Vector (1 1) \n"); 
  ziscCARD.JZ_PutVector(ziscCARD.array, 2);    /* input components */ 
  ziscCARD.JZ_PutCat(1);              /* learn category 0 */ 

Listing 4: Sample output.
Recognize pair (0 1) 

Status content after input vector 

error flag= 0 
deg= 0 
unc= 0 
full= 0 
id= 1 
Sequence of read dist read cat 

CORRECT: dist = 0 Out= 1 



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.