This material is published with permission of Informant Communications Group, Inc. and Delphi Informant magazine.

Greater Delphi

Java Native Interface / Java / JBuilder / Delphi 6, 7

By Keith Wood

Going Native: Part 1

Using the Java Native Interface with Delphi

Java and Delphi serve different purposes as programming languages, each with their own advantages and disadvantages. They can easily communicate by transferring data via files, sockets, or Web Services. However, there are times when you may want to converse between the two in a more direct manner. The Java Native Interface (JNI) was developed for such interaction, and this article introduces you to using Delphi and Java through the JNI.

JNI allows bi-directional calling between Java and "native" (e.g. Delphi) code. You can create, update, and inspect Java objects from within your native code. You can call methods (both static and non-static) within Java classes. You can capture and deal with Java exceptions within native code, or generate exceptions back to the Java runtime.

Some reasons that you might want to call native code from Java include: gaining access to platform-specific features not available in Java, reusing existing code libraries already in native format for which you do not have the source code or the time to port, or for running time-critical or CPU-hungry routines in a faster environment. Similarly, you may want to call Java from native code to use existing packages and APIs, or to gain access to Java-specific constructs, such as RMI and EJBs.

Although originally developed with C and C++ as the target native languages, the efforts of Matthew Mead have produced a Pascal translation of the JNI suitable for use in Delphi. The package is available from Matthew’s Web site (see the "References" section at the end of this article). Included in the package are several examples and tutorials to get you started.

Java Types

Communication between the two environments consists of making method calls and passing parameters back and forth. The arguments may be either primitive types or complete objects. Obviously there needs to be some mapping between the basic types in Java and those in Delphi to facilitate their transfer. The JNI.pas and JNI_MD.inc files contain these mappings, as shown in Figure 1. So, wherever you use a particular type in Java, you should use the corresponding J* type defined in Delphi. Behind the scenes these are just renamings of the associated basic Delphi types.

Java Type Delphi Type Type Name Signature
boolean Boolean JBoolean Z
byte ShortInt JByte B
char WideChar JChar C
double Double JDouble D
float Single JFloat F
int Integer JInt I
long Int64 JLong J
short SmallInt JShort S
void N/A N/A V

Figure 1: Corresponding Java and Delphi types.

Every other type is passed generically as an object and is referred to in Delphi as JObject. Several renamings of this base type appear, including JString, JClass, and JArray (and its derivatives).

Associated with each type is a signature character. This short form is used to identify types for fields, and for the return and parameter types of methods. Class types have a signature that includes the full package and class name (replacing periods, ‘.’, with slashes, ‘/’), prefixed by ‘L’ and suffixed by a semi-colon ( ; ), such as ‘Ljava/awt/Rectangle;’. Array references are prefixed by an opening bracket ( [ ), one for each dimension. Thus, the signature ‘[[I’ represents a two-dimensional array of integers.

Method Declarations

Java methods can be linked to external routines through the use of the native modifier, as shown below from the Object class. Such a method has no body (because this is provided by the external library), but is not declared as abstract:

public final native Class getClass();

Your native code is kept in a shared library — a DLL under Windows or an SO under Linux. To make the library available within Java you must include a static initializer in your class (see Figure 2). The appropriate prefix and extension are added to the base library name depending on your platform. Thus the class in Figure 2 looks for MyLib.dll on Windows, or libMyLib.so on Linux.

package mypkg;

public class MyClass { 
  static { 
    System.loadLibrary("MyLib");
  }
  public native void doIt(String message);
}

Figure 2: Loading a native library from Java.

The signatures of the routines within your native library must follow specific conventions to be useable from Java. First, they must be named in a particular format:

Java_<full_class_name>_<methodName><__overloaded_signature>

where <full_class_name> is the full package and class name, with periods replaced by underscores. Remember that, unlike Delphi, case is important in Java and that this carries over into these method names. Always use the same case as appears in the Java declarations.

Native methods with overloaded versions must include a suffix that indicates which method signature is being referenced. Using the signature characters described earlier, append these to the method name, following two underscores. In addition, certain characters that cannot appear in a method name must be "escaped": Use ‘_1’ for ‘_’, ‘_2’ for ‘;’, and ‘_3’ for ‘[‘. Thus, the getClass method previously referred to becomes:

Java_java_lang_Object_getClass

in Delphi, and the overloaded wait routine from the Object class:

public final native void wait(long timeout);

becomes:

Java_java_lang_Object_wait__J

Every routine must take at least two parameters: a reference to the JNI environment, and a reference to the object making this call (or class if static). Additional parameters are included to match those of the Java declaration. Also, the routines must follow the standard library calling convention for the platform: stdcall on Windows or cdecl on Linux. Thus, the full Delphi signature for the doIt method defined in Figure 2 is:

procedure Java_mypkg_MyClass_doIt(PEnv: PJNIEnv;
  Obj: JObject; message: JString);
  {$IFDEF WIN32} stdcall; {$ENDIF}
  {$IFDEF LINUX} cdecl; {$ENDIF}

Finally, these routines must be exported from the library to make them accessible from Java:

exports Java_mypkg_MyClass_doIt;

The JNI Environment

The first parameter in any native method call is a pointer reference to the JNI environment, PEnv. It’s through this that you access the Java runtime. Delphi/JNI includes a class wrapper, TJNIEnv, for this pointer to make it easier to deal with. Thus the body of your methods often includes the code shown in Figure 3.

procedure Java_mypkg_MyClass_doIt(PEnv: PJNIEnv;
  Obj: JObject; Message: JString);
  {$IFDEF WIN32} stdcall; {$ENDIF}
  {$IFDEF LINUX} cdecl; {$ENDIF}
var
  JVM: TJNIEnv;
begin
  // Create an instance of the Java environment.
  JVM := TJNIEnv.Create(PEnv);
  try
    // Perform the task for this routine.
  finally
    JVM.Free;
  end;
end;

Figure 3: A Delphi JNI routine skeleton.

Through the environment object you can load classes and create instances of them (FindClass, NewObjectA, etc.), access the fields and methods of an object (GetIntField, CallObjectMethod, etc.), work with arrays (NewByteArray, GetCharArrayElements, etc.), and handle and generate exceptions (ExceptionOccurred, ThrowNew, etc.). Have a look at the source code (available for download; see end of article for details), or use CodeInsight, for the full list.

JavaD

The Java Development Kit (JDK) comes with a tool, javah.exe, that generates appropriate header and source files for using JNI with C. To make it easier to use Delphi with Java, I’ve developed a similar tool that generates the appropriate Delphi (Pascal) skeleton for native methods. (Matthew includes a similar one named JavaToDPR in the Delphi/JNI package.

The tool I developed is implemented as a Java class, wood.keith.opentools.javad.JavaD, which is accessible via a number of channels. You may call this class’ main method from the command line, passing an optional flag, ‘-o’, to indicate the overwriting of existing files, the full name of the class to examine, and the location of the directory where the resulting file is to be placed. The generated file is named the same as the full class name provided (replacing periods, ‘.’, with underscores, ‘_’), with a .dpr extension:

java -cp <path to both classes> 
  wood.keith.opentools.javad.JavaD
  -o wood.keith.djniexample.Example1 c:\Projects\DJNI

where <path to both classes> contains the directories at the top of the class file hierarchies for the two classes, separated by a semi-colon ( ; ).

The Delphi code produced includes the library declaration, skeletons like Figure 3 for each public native method (that’s not derived from Object) found in the nominated class, and an exports clause for each such routine.

Alternatively, you can access the generator via a JBuilder OpenTool that’s included in the package. Simply place the tool’s JAR file into your JBuilder/lib/ext directory, and restart JBuilder. In the Object Gallery’s (File | New on the menu) General tab, you now have an icon for JavaD. Start it up, enter the class and output directory names, and proceed to generate the Delphi skeleton (and optionally open it in JBuilder).

Finally, to demonstrate how to call Java code from Delphi, you’ll see later how to invoke the generator from a Delphi GUI. You can easily add code to the routines to perform the appropriate tasks after the Delphi skeleton is generated from your Java class declarations.

Java Calling Delphi

To illustrate how to call Delphi code from Java, suppose you have some data in a Delphi-accessible database that you wish to use in your Java application. If an appropriate JDBC driver (Java’s version of ODBC) isn’t available, you could call a Delphi library to retrieve it instead. For this example, query the BioLife database that ships with Delphi to find fish that meet certain criteria. Matching entries have their details collated into a data value object (an object with fields only, a bit like a Pascal record) that’s defined in Java, and are passed back to the calling code in an array. If no matches are found, an exception is raised to indicate the problem.

Start by defining your Java class, wood.keith.djniexample.Example1, and its native methods (as shown in Figure 4). In addition to the database search methods, there’s a printMessage method to show how Delphi can write to the Java console.

package wood.keith.djniexample;

/* Demonstrate JNI access to Delphi library. */
public class Example1 { 
  static { 
    System.out.println("Library path: " +
      System.getProperty("java.library.path"));
    // Load the native library.
    System.loadLibrary("wood_keith_djniexample_Example1");
  }

  public Example1() {}
  /* Display the message to the console.
   * @param  message  the text to display  */
  public static native void printMessage(String message);

  /* Find all BioLife database records.
   * @return  an array of matching BioLife records,
   *          empty if none   */
  public native BioLife[] getBioLife();

  /* Find BioLife database records matching given category.
   * @param  category  the (partial) category to match
   * @return  an array of matching BioLife records,
   *          empty if none   */
  public native BioLife[] getBioLife(String category);

  /* Find BioLife database records matching given lengths.
   * @param  minLength  the minimum length required
   * @param  maxLength  the maximum length required
   * @return  an array of matching BioLife records,
   *          empty if none   */
  public native BioLife[] getBioLife(
    int minLength, int maxLength);

  // ...supporting code removed...

  /* Demonstrate calling native methods.
   * @param  args  command-line parameters  */
  public static void main(String[] args) { 
    Example1 example1 = new Example1();
    printMessage("Starting example1");
    example1.performSearch();
    example1.performSearch("Ray");
    example1.performSearch("XRay");
    example1.performSearch(20, 40);
    printMessage("Ending example1");
  }
}

Figure 4: Example native methods to access the BioLife database.

As mentioned, any matching database records are placed into a data value object for transfer back to the Java application. The definition of that object is shown in Figure 5. It consists mainly of a number of fields (corresponding to the database fields of interest) and their accessor methods. Also included is an override of the standard toString method to make it easier to display these objects after they’ve been retrieved.

/* Value object for BioLife records. */
class BioLife { 
  private int _speciesNo;
  private String _category;
  private String _commonName;
  private String _speciesName;
  private int _lengthCM;
  private double _lengthIN;

  public BioLife(int speciesNo, String category,
      String commonName, String speciesName,
      int lengthCM, double lengthIN) { 
    _speciesNo = speciesNo;
    _category = category;
    _commonName = commonName;
    _speciesName = speciesName;
    _lengthCM = lengthCM;
    _lengthIN = lengthIN;
  }

  public int getSpeciesNo() { return _speciesNo; }
  public String getCategory() { return _category; }
  public String getCommonName() { return _commonName; }
  public String getSpeciesName() { return _speciesName; }
  public int getLengthCM() { return _lengthCM; }
  public double getLengthIN() { return _lengthIN; }
  public String toString() { 
    return _commonName + " (" + _speciesName +
      ") - length " + _lengthCM + "cm (" +
      (Math.round(_lengthIN * 100) / 100.0) + "in)";
  }
}

Figure 5: A data value object for BioLife records.

The Delphi Code

After running the JavaD tool on the Example1 Java class, you obtain the skeleton Delphi library file, wood_keith_djniexample_Example1.dpr, the final version of which is shown in Listing One. Aside from the searchBioLife routine, and the central statements in each of the native implementations, all this code is generated for you.

Within the Delphi code, each getBioLife routine delegates to a common function, searchBioLife, passing along the JVM reference, and a WHERE clause for the database query that reflects the original parameters. Within searchBioLife, you create the query object, set its properties, and open the query.

An exception is thrown within the Java application via the JVM.ThrowNew call, if no records match the given criteria. To achieve this, you first need a reference to the exception class being created. The earlier call to JVM.FindClass returns that class reference, given the full name of the required class (with periods replaced by slashes). Note that throwing this Java exception does not halt processing within the Delphi code. You should exit the routine immediately after the call. The exception is caught normally within the Java code, and the message and stack trace are displayed as expected.

When matching records are found, each one is read and has its field values copied into a new Java BioLife object. Again, you call JVM.FindClass to locate the data value class, then JVM.NewObjectA to create an instance of that class. A particular constructor for the class is selected by identifying its parameter signature within a JVM.GetMethodID call. The constructor’s ID is passed into the creation call, along with an array of values matching that signature in number and type.

To make JNI easier to use, I’ve included a JNIUtils unit with the code. Among its features is a function to convert a Java version of a method signature into the coded form described earlier. For example:

GetJavaMethodSig(
  'void (int, String, String, String, int, double)')

returns:

'(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;ID)V'

relieving you from having to remember the actual type codes. This method is used within the CreateObject call to help locate the appropriate constructor to use. The unit also includes a constant, ConstructorName, that holds the internal Java name for an object constructor. The use of this unit is controlled within this demonstration code by the JNIUTILS conditional define.

Basic class types, such as String and Object, can be used in the Java signatures call above without specifying their full path. This is because they have had their short name previously registered against the full class name. The common types are predefined, but you can add your own shortcuts to this mechanism by calling AddCommonJavaClass and supplying the full class name, and an optional shortcut name (the class name without the package is used by default).

The array that is the result of the searchBioLife function is created through the JVM.NewObjectArray call, which takes the number of items in the array, the type of object stored in the array, and a set of initial values (or nil for none). Each BioLife object is added to the array with the JVM.SetObjectArrayElement call. Back in Java, the array is stepped through, and the toString method of each BioLife object allows us to easily display the results.

Running Native Code from Java

When your Delphi library is compiled, you must place it where the Java code can find it (somewhere on the system path). The first line in the static initializer for the Java class prints out the full path used to locate the library, as retrieved from the java.library.path system property, to assist in resolving loading problems. Then you can run the Example1 class via its main method:

java -cp <path to class> wood.keith.djniexample.Example1

where <path to class> is the directory at the top of your Java class file hierarchy.

The resulting output should look like that shown in Figure 6. You can see the BioLife entries found, as well as the exception generated when no matches result. Note that the console output from the Delphi printMessage procedure (the time-stamped entries) is not synchronized with output produced by the Java class.

Searching fish for everything
Found Clown Triggerfish (Ballistoides conspicillum) -
  length 50cm (19.69in)
Found Red Emperor (Lutjanus sebae) -
  length 60cm (23.62in)
  :
Found Senorita (Oxyjulis californica) -
  length 25cm (9.84in)
Found Surf Smelt (Hypomesus pretiosus) -
  length 25cm (9.84in)

Searching fish for "Ray"
Found Bat Ray (Myliobatis californica) -
  length 56cm (22.05in)
Found Spotted Eagle Ray (Aetobatus narinari) -
  length 200cm (78.74in)

Searching fish for "XRay"
java.lang.Exception: No matching fish found
  at wood.keith.djniexample.Example1.getBioLife
    (Native Method)
  at wood.keith.djniexample.Example1.performSearch
    (Example1.java:74)
  at wood.keith.djniexample.Example1.main
    (Example1.java:116)
Found none

Searching fish between 20 and 40cm
Found Blue Angelfish (Pomacanthus nauarchus) -
  length 30cm (11.81in)
Found Firefish (Pterois volitans) -
  length 38cm (14.96in)
Found Redband Parrotfish (Sparisoma Aurofrenatum) -
  length 28cm (11.02in)
Found French Grunt (Haemulon flavolineatum) -
  length 30cm (11.81in)
Found Redtail Surfperch (Amphistichus rhodoterus) -
  length 40cm (15.75in)
Found Senorita (Oxyjulis californica) -
  length 25cm (9.84in)
Found Surf Smelt (Hypomesus pretiosus) -
  length 25cm (9.84in)
Starting example1 at 14/04/2003 10:39:30 PM
Ending example1 at 14/04/2003 10:39:33 PM

Figure 6: Output from running the Example1 Java class.

Conclusion

The Java Native Interface lets you call native code from Java code, or vice versa. A Delphi version of the JNI is available because of the efforts of Matthew Mead. Create your Java class with its native method declarations, add the JNI unit to your Delphi library, follow certain naming and parameters conventions, and your interactions with Java follow effortlessly.

To call native code from Java, use the JavaD class that accompanies this article to generate Pascal skeletons for Java native methods. Then complete the routines to implement your native code requirements. The JNIUtils unit can make interactions with the JVM a little less onerous by encoding type and method signatures for you, as well as by handling the various steps involved in calling a method or retrieving a field value on a Java object.

The demonstration program described herein calls Delphi code from a Java class to extract data from a database. Matching records are copied to Java-defined data value objects and added to an array. The array is then passed back to Java for its contents to be displayed. A Java exception is thrown if no matches are found, leaving it to the calling class to deal with the error.

In Part 2 you’ll see how to invoke Java classes from Delphi code, fulfilling the bi-directional promise of JNI. Until then, you can download the Delphi/JNI package and look at the examples and documentation that it provides. With Java and Delphi now on speaking terms, the possibilities are greatly expanded for both.

JavaD and the Java and Delphi demo programs referenced in this article are available for download.

References

JNI Specification: http://java.sun.com/j2se/1.4.1/docs/guide/jni/index.html

JNI FAQ: http://java.sun.com/products/jdk/faq/jnifaq.html

Delphi/JNI Home: http://home.pacifier.com/~mmead/jni/delphi/index.html

Keith Wood hails from Australia, where he is a consultant working with Java and Delphi, and a freelance technical writer. He started using Borland’s products with Turbo Pascal on a CP/M machine. His book, Delphi Developer’s Guide to XML, 2nd Edition from BookSurge, covers many aspects of XML from a Delphi point of view. You can reach him via e-mail at kbwood@iprimus.com.au.

Begin Listing One — Delphi implementation of Example1 methods

library wood_keith_djniexample_Example1;
{ Pascal skeleton for using JNI with the
  wood.keith.djniexample.Example1 Java class.
  Generated Thu Apr 03 22:44:28 GMT+10:00 2003 by JavaD.
  Updated by Keith Wood (kbwood@iprimus.com.au). }
uses
  SysUtils, JNI,
{$IFDEF JNIUTILS}
  JNIUtils,
{$ENDIF}
  DB, DBTables;

{ Java declaration: 
  public static native void printMessage(java.lang.String)
  Class: wood.keith.djniexample.Example1
  Method: printMessage
  Signature: (Ljava/lang/String;)V
  Display the message provided. }
procedure
  Java_wood_keith_djniexample_Example1_printMessage(
  PEnv: PJNIEnv; Obj: JObject; message: JString);
  {$IFDEF WIN32} stdcall; {$ENDIF}
  {$IFDEF LINUX} cdecl; {$ENDIF}
var
  JVM: TJNIEnv;
begin
  JVM := TJNIEnv.Create(PEnv);
  try
    Writeln(JVM.JStringToString(Message) + ' at ' +
      DateTimeToStr(Now));
  finally
    JVM.Free;
  end;
end;

{ Search BioLife database, given a query, 
  and return matches.}
function searchBioLife(JVM: TJNIEnv;
  QueryWhere: string): JObjectArray;
var
  BioLifeQuery: TQuery;
  ExceptionClass: JObject;
  BioLifeClass: JObject;
{$IFNDEF JNIUTILS}
  ConstructorId: JMethodID;
{$ENDIF}
  BioLifeObj: JObject;
  Index: Integer;
begin
  Result       := nil;
  BioLifeQuery := TQuery.Create(nil);
  try
    // Find exception classes.
    ExceptionClass := JVM.FindClass('java/lang/Exception');
    if not Assigned(ExceptionClass) then
      Writeln('Cannot find Exception class');
    // Find BioLife value object class...
    BioLifeClass :=
      JVM.FindClass('wood/keith/djniexample/BioLife');
    if not Assigned(BioLifeClass) then
      Exit;
{$IFNDEF JNIUTILS}
    // ...and its constructor.
    ConstructorId := JVM.GetMethodID(BioLifeClass,
      '<init>', '(ILjava/lang/String;Ljava/lang/String;' +
      'Ljava/lang/String;ID)V');
    if not Assigned(ConstructorId) then
      Exit;
{$ENDIF}
    // Run the query.
    with BioLifeQuery do begin
      DatabaseName := 'DBDemos';
      SQL.Text     := 'select BioLife."Species No", ' +
        'Category, Common_Name, BioLife."Species Name", ' +
        'BioLife."Length (cm)", Length_In ' +
        'from BioLife where ' + QueryWhere;
      Open;
      if RecordCount = 0 then begin
        JVM.ThrowNew(
          ExceptionClass, 'No matching fish found');
        Exit;
      end;
      // Create the result array.
      Result := JVM.NewObjectArray(
        RecordCount, BioLifeClass, nil);
      Index  := 0;
      while not EOF do begin
        // Create a BioLife object and populate it.
{$IFDEF JNIUTILS}
        BioLifeObj := JNIUtils.CreateObject(JVM,
          'wood.keith.djniexample.BioLife',
          'void (int, String, String, String, int, double)',
          [FieldByName('Species No').AsInteger,
          FieldByName('Category').AsString,
          FieldByName('Common_Name').AsString,
          FieldByName('Species Name').AsString,
          FieldByName('Length (cm)').AsInteger,
          FieldByName('Length_In').AsFloat]);
{$ELSE}
        BioLifeObj := JVM.NewObjectA(BioLifeClass,
          ConstructorId, JVM.ArgsToJValues(
          [FieldByName('Species No').AsInteger,
          FieldByName('Category').AsString,
          FieldByName('Common_Name').AsString,
          FieldByName('Species Name').AsString,
          FieldByName('Length (cm)').AsInteger,
          FieldByName('Length_In').AsFloat]));
{$ENDIF}
        if not Assigned(BioLifeClass) then
          Exit;
        // Add it to the array.
        JVM.SetObjectArrayElement(
          Result, Index, BioLifeObj);
        Inc(Index);
        Next;
      end;
      Close;
    end;
  finally
    BioLifeQuery.Free;
  end;
end;

{ Java declaration: public native
    wood.keith.djniexample.BioLife[] getBioLife()
  Class: wood.keith.djniexample.Example1
  Method: getBioLife
  Signature: ()[Lwood/keith/djniexample/BioLife;
  Find all BioLife records. }
function
  Java_wood_keith_djniexample_Example1_getBioLife__(
  PEnv: PJNIEnv; Obj: JObject; Category: JString):
  JObjectArray; {$IFDEF WIN32} stdcall; {$ENDIF}
  {$IFDEF LINUX} cdecl; {$ENDIF}
var
  JVM: TJNIEnv;
begin
  JVM := TJNIEnv.Create(PEnv);
  try
    Result := searchBioLife(
      JVM, 'BioLife."Length (cm)" > 0');
  finally
    JVM.Free;
  end;
end;

{ Java declaration: public native
    wood.keith.djniexample.BioLife[]
    getBioLife(java.lang.String)
  Class: wood.keith.djniexample.Example1
  Method: getBioLife
  Signature: (Ljava/lang/String;)
    [Lwood/keith/djniexample/BioLife;
  Find BioLife records like a given category. }
function Java_wood_keith_djniexample_Example1_
getBioLife__Ljava_lang_String_2(
  PEnv: PJNIEnv; Obj: JObject; Category: JString):
  JObjectArray; {$IFDEF WIN32} stdcall; {$ENDIF}
  {$IFDEF LINUX} cdecl; {$ENDIF}
var
  JVM: TJNIEnv;
begin
  JVM := TJNIEnv.Create(PEnv);
  try
    Result := searchBioLife(JVM, 'Category like "%' +
      JVM.JStringToString(Category) + '%"');
  finally
    JVM.Free;
  end;
end;

{ Java declaration: public native
    wood.keith.djniexample.BioLife[] getBioLife(int, int)
  Class: wood.keith.djniexample.Example1
  Method: getBioLife
  Signature: (II)[Lwood/keith/djniexample/BioLife;
  Find BioLife records with length (cm) in a given range. }
function
  Java_wood_keith_djniexample_Example1_getBioLife__II(
  PEnv: PJNIEnv; Obj: JObject; MinLength: JInt;
  MaxLength: JInt): JObjectArray;
  {$IFDEF WIN32} stdcall; {$ENDIF}
  {$IFDEF LINUX} cdecl; {$ENDIF}
var
  JVM: TJNIEnv;
begin
  JVM := TJNIEnv.Create(PEnv);
  try
    Result := searchBioLife(JVM, 'BioLife."Length (cm)" ' +
      'between ' + IntToStr(MinLength) +
      ' and ' + IntToStr(MaxLength));
  finally
    JVM.Free;
  end;
end;

exports
  // Make the routines available for external use.
  Java_wood_keith_djniexample_Example1_printMessage,
  Java_wood_keith_djniexample_Example1_getBioLife__,
  Java_wood_keith_djniexample_Example1_getBioLife__
Ljava_lang_String_2,
  Java_wood_keith_djniexample_Example1_getBioLife__II;
end.

End Listing One