您的位置:首页 > 编程语言 > Delphi

使用Delphi实现JNI - 2

2015-06-01 00:16 579 查看
原文地址:http://www.pacifier.com/~mmead/jni/delphi/JEDI/DOCS/delphi-jni-2.html

Using the Java Native Interface with Delphi

(Part Two - Techniques and Examples)

Matthew Mead

Home Page (on the Web) - News and Updates

Part One - Introduction and Tutorial

Part Two - Techniques and Examples

Part Three - The Invocation API





Contents

OverviewWhat's here?
SignaturesHow to describe a function's parameters and return type
ConceptsThese are examples of what you will learn here. The test in brackets (e.g. [Test 4]), indicates the test number that demonstrates this in the included code. Examples that don't include a test number are still present in the code, however, they were not
part of the original C++ implementation so they don't have a test case assigned.
Simple Function Call - Call a Delphi procedure that doesn't take any arguments. [Test 1]
Passing/Returning Simple Types - Demonstrates passing 2 integer values to a function that multiplies them together and then returns the result.
[Test A]
Passing an Array of Primitive Types to Delphi - Demonstrates passing a Java Array (of type byte) to the Delphi DLL. The DLL initializes each element
of the Java Array. This example also shows how to overload a native method.
Passing an Array of Objects to Delphi - Demonstrates passing a Java Array of objects to the Delphi DLL. This example also shows how to access each
object in the array and print it out using the object's toString method. (This method is present in almost all Java objects.) In order to invoke the toString method, the Delphi DLL will need to call back
into Java. [Tests 5, 6]
Passing a 2-Dimensional Array to Delphi - Demonstrates passing a 2-dimensional array to Delphi. The method shown here can be applied to 3-dimensional
arrays as well. [Test 10]
Calling a Method of a Java Object from Delphi - Demonstrates invoking a method on a Java object that was passed into the DLL. (See Concept #4, Passing
an Array of Objects to Delphi for the example.)
Creating Java Objects within Delphi - Demonstrates creating Java objects in Delphi and passing them back to Java. [Test 7]
Accessing Fields of a Java Object from Delphi - Demonstrates accessing fields (members) of a Java object that was passed into the DLL. [Test 4]
Handling Java Exceptions within Delphi - Demonstrates how to detect and suppress Java exceptions within Delphi. [Test 8]
Causing a Java Exception within Delphi - Demonstrates how to cause (throw) a Java exception. [Test 9]

TJNIEnvA closer look at the TJNIEnv wrapper class.
SummaryWhat's next?
Overview
In Part One, I gave an introduction to Using the Java Native Interface with Delphi. I showed (in painful detail) an example of printing "Hello
World!" from a Delphi DLL called from Java. In Part Two, I will show more sophisticated techniques using the Java Native Interface with Delphi. You will learn concepts such as how to access fields of Java objects and how to call methods of a Java class.
You will also see how to create Java objects within a Delphi DLL and pass them back and forth between Java and Delphi. Essentially, these are some of the concepts discussed in this document:

Calling Delphi code from Java.
Passing parameters to Delphi code from Java. This includes Java Strings, arrays, and other Java objects, as well as basic types (e.g. int, long, etc.)
Returning values from Delphi code to Java. This includes Java Strings, arrays, and other Java objects, as well as basic types (e.g. int, long, etc.)
Calling Java methods from Delphi code (callbacks). This should include both static and non-static methods.
Accessing fields of Java classes and objects from Delphi code. This should include both static and non-static fields.
Detecting and handling Java exceptions from within Delphi code.

This part of Using the Java Native Interface with Delphi is based on some JNI research I did using C++ a couple of years or so ago. This work can be found here.
For this document, I essentially translated the C++ DLL to a Delphi DLL. (The lines of C++ code translated to Delphi code almost one-for-one.) In this version of that work, I won't be explaining a lot of the fundamentals of using the JNI. You can refer to Part
One of Using the Java Native Interface with Delphi for explanations of those concepts. This document focuses on putting those fundamentals to use.

This part takes more of a Learn By Example approach to the JNI. Because of this, I don't spend a lot of time explaining in detail each of the many JNI functions that are used. I leave that up to Sun's
JNI Documentation. This document is meant to provide a convenient place to find out how to do common tasks when using the Java Native Interface. The examples here are not exhaustive, meaning that I don't have examples for everything you'd want
to do with the JNI. As I encounter other/better examples, I will add them here.

One last thing: My original work with these JNI concepts also included some rudimentary benchmarks. One of the goals was to compare the performance between Sun's JDK and Microsoft's JDK, as well as the performance differences on Windows, Solaris, and Linux.
This document doesn't discuss those goals, however, some of those tests still exist in the Java files. Rather than remove them, I just left them in the files with the hope that, at some later point, I might come back and revisit them.

Back to top

Signatures
A function's signature consists of the function's name and parameters. The function's parameters are subject to these constraints:

number - A function expects a certain number of parameters (some can handle a variable number of parameters)
type - A function expects each parameter to be of a specific type
order - A function expects the parameters in a known (fixed) order

Note that not all languages have these criteria. For example, Ada, Python, and Visual Basic (among others) can pass parameters by Name, so the order is not required.
There are several functions in the JNI that require, as a parameter, the signature of another function. The type of this parameter is a string. The table below shows how Java types are mapped into characters (e.g. boolean is mapped as the uppercase letter Z)
and strings (e.g. Java String is mapped as the literal string Ljava/lang/String;). It then just becomes a matter of building these strings up based on the parameters and return types of Java methods.
Java TypeSignature
booleanZ
byteB
charC
doubleD
floatF
intI
longJ
voidV
objectLfully-qualified-class;
type[][type
method signature( arg-types) ret-type
Examples:

MethodSignature
void f1()()V
int f2(int, long)(IJ)I
boolean f3(int[])([I)B
double f4(String, int)(Ljava/lang/String;I)D
void f5(int, String [], char)(I[Ljava/lang/String;C)V
If you recall from Part One, all native functions have two hidden parameters. These parameters are not included in this signature (since these signatures
refer to Java methods.) So, for example, a Java method that doesn't take any arguments and doesn't return a value would have the signature:

()V


Later, you will see how these signatures are used by Delphi when invoking Java methods.
Back to top

Concepts
In this document, each concept is explained with an example that implements some code in Java and/or Delphi. For example, one concept to learn is how to call a void function (a procedure in Delphi) that
doesn't take any arguments. This is done in this document by creating a procedure in Delphi and printing the words Hello World! to the screen.
As in Part One, this part is implemented in 3 files:

Native.java This is a Java class that declares the native methods.
Native.dpr This is the Delphi code that implements the native methods.
Main.java This is the Java program that calls the native (Delphi) methods.

Each function contains a header comment that shows:

The Java declaration - This is how the Delphi function/procedure would be declared in a Java class.
The Java class that declares the function - Our functions are declared in Native.java.
The name of the method - This is the name that the Java class sees (without the decorations.)
The signature - The format of these signatures is defined by the JNI and is described above.

Incidentally, most of the function comments can be automatically generated with javah, a tool that creates C/C++ header files from Java class files. I used this tool to create this information and then copied it into the Delphi code.
Back to top

Concept #1 - Simple Function Call
This is an example of a function that doesn't take any parameters and doesn't return any value. This is essentially the Hello World function described in Part
One.
(****************************************************************************
*  Java declaration:
*     native public void displayHelloWorld();
*
*  The canonical first method.
*
*  Class:     Native
*  Method:    displayHelloWorld
*  Signature: ()V
*)
procedure Java_Native_displayHelloWorld(PEnv: PJNIEnv; Obj: JObject); stdcall;
begin
WriteLn('Hello world!');
end;

Back to top

Concept #2 - Passing/Returning Simple Types
This is an example of a function that expects 2 integer parameters, multiplies them together, and returns the result.
(****************************************************************************
*  Java declaration:
*     native public void multiplyIntegers(int op1, int op2);
*
*  Multiplies 2 integers and returns the result.
*
*  Class:     Native
*  Method:    multiplyIntegers
*  Signature: (II)I
*)
function Java_Native_multiplyIntegers(PEnv: PJNIEnv; Obj: JObject; Op1: JInt; Op2: JInt): JInt; stdcall;
begin
Result := Op1 * Op2;
end;

Back to top

Concept #3 - Passing an Array of Primitive Types to Delphi
This example shows how to pass a Java Array to a Delphi method. Each element of the array is set to a value (specified as a parameter.) The native method initializeByteArray is an overloadedmethod within the Delphi
DLL. The original initializeByteArray method required an extra parameter: the number of times to initialize the array. Obviously, you only need to initialize each member once, but the original version is used to test the performance
of initializing an array many times. However, this example only requires a single initialization, so I overloaded the method and exported it with the name Java_Native_initializeByteArrayOnce. This has the benefits of allowing both
versions to be called from Java and also maintains compatibility with the existing Java code, namely Main.java.
(****************************************************************************
*  Java declaration:
*     native public void initializeByteArray(byte[] byteArray, int count, byte value);
*
*  Initializes an array 'Count' times with the Value 'Value'. This function
*  is mainly used to test the performance of array accesses in native code.
*
*  Class:     Native
*  Method:    initializeByteArray
*  Signature: ([BIB)V
*)
procedure Java_Native_initializeByteArray(PEnv: PJNIEnv; Obj: JObject; ByteArray: JByteArray; Count: JInt; Value: JByte); {$IFDEF WIN32} stdcall; {$ENDIF} {$IFDEF LINUX} cdecl; {$ENDIF} overload;
var
Elements: PJByte;
PE: PJByte;
Len: JSize;
I, J: Longint;
IsCopy: JBoolean;
JVM: TJNIEnv;
begin
// Create an instance of the Java environment
JVM := TJNIEnv.Create(PEnv);

// Get elements of the array
Elements := JVM.GetByteArrayElements(ByteArray, IsCopy);

// Get length of the array
Len := JVM.GetArrayLength(ByteArray);

// Loop through the array 'Count' times, assigning 'Value' to each
// element.
for I := 0 to Count - 1 do
begin
PE := Elements;
for J := 0 to Len - 1 do
begin
PE^ := Value;
Inc(PE);
end;
end;

// Important! From Sun's documentation:
// Since the returned array may be a copy of the Java array, changes made
// to the returned array will not necessarily be reflected in the original
// array until Release<PrimitiveType>ArrayElements() is called.
JVM.ReleaseByteArrayElements(ByteArray, Elements, 0);

end;


Notes:

Because elements is a pointer and not an array type, you can't use array indexing (e.g. elements[i]). I use another pointer to walk the array, thus preserving the value of elements, which is later passed to ReleaseByteArrayElements to
be released.
Check out the description of the JNI methods GetXXXArrayElements and ReleaseXXXArrayElements on
Sun's site for details. You'll see an explanation of the parameters that are passed to these methods. (These links explain it better than I can.)

Back to top

Concept #4 - Passing an Array of Objects to Delphi
This example shows how to access each element of an array of Java objects. For each object in the array, the object's toString method will be invoked on the object. Almost all Java objects implement the toString method,
which prints something intelligent about the value of the object. There are some interesting concepts to recognize here:

It doesn't matter what kind of object is in the array. This function will handle all types and the toString method will act correctly (since it's being invoked on the object itself.)
The DLL is actually calling back into Java to do the printing. This is because, although the object has been passed into the DLL (as an element of the array), it's really only a reference to the object. The object's methods still exist in the Java code
that called the DLL.

(****************************************************************************
*  Java declaration:
*     native public void printObjectArray(Object[] array);
*
*  Given an array of Objects, each element is printed using the
*  'toString' method of the Object.
*
*  Class:     Native
*  Method:    printObjectArray
*  Signature: ([Ljava/lang/Object;Z)V
*)
procedure Java_Native_printObjectArray(PEnv: PJNIEnv; Obj: JObject; ObjArray: JObjectArray; Print: JBoolean); stdcall;
var
Cls: JClass;
Mid: JMethodID;
Ob: JObject;
Element: JObject;
S: JString;
Str: string;
Len, I: Integer;
JVM: TJNIEnv;
begin
JVM := TJNIEnv.Create(PEnv);

// Get length of the array
len := JVM.GetArrayLength(ObjArray);

// Make sure we have at least one object in the array
if Len < 1 then
exit;

// Get an element of the array so we can get the right 'toString' method
Ob := JVM.GetObjectArrayElement(ObjArray, 0);

// Get the class associated with this object
Cls := JVM.GetObjectClass(Ob);

if Cls = nil then begin
WriteLn('Can''t GetObjectClass');
exit;
end;

// Get method ID for the 'toString' method of this object's class
Mid := JVM.GetMethodID(Cls, 'toString', '()Ljava/lang/String;');

// We will check this also
if Mid = nil then begin
WriteLn('Can''t GetMethodID for toString');
exit;
end;

// Loop the array of objects and print out each one using
// the 'toString' method of its ancestor class Object
for I := 0 to Len - 1 do begin

// Get the next element from the array
Element := JVM.GetObjectArrayElement(ObjArray, i);

// Call the 'toString' method, which returns a String representation
// of Rectangle object.
S := JVM.CallObjectMethodA(Element, Mid, nil);

// The actual printing can be turned on/off. This was useful during
// debugging when passing thousands of elements into the procedure.
if Print <> False then begin
Str := JVM.JStringToString(S);
WriteLn(Str);
end;
end;

JVM.Free;
end;


Notes:

The GetMethodID function is used to locate the object's method that we want to invoke. This function takes 4 arguments:

env - The Java environment. This is the first parameter to all JNI functions.
cls - This is the object's class. We got this by calling the GetObjectClass function in the previous line. The method that we want to invoke resides in the object's class somewhere.
'toString' - This is the name of the method. The name is case-sensitive.
'()Ljava/lang/String;' - This is the method's signature. The toString takes 0 parameters and returns a String.

Additional note: The GetMethodID only takes 3 arguments in the code above. This is because we are using the TJNIEnv class to wrap the raw interface. Thus, the env parameter does not need to be
passed because it is part of the class. When the class in turn calls the JNI function GetMethodID, it will at that time pass env as a parameter.

The CallObjectMethodA function is what actually invokes (calls) the method. This function also takes 4 arguments:

env - The Java environment.
element - This is the object. In this example, element is one element of the array of objects.
mid - This is the ID of the method we are invoking. This is why we needed the GetMethodID above.
nil - The last parameter is an array of parameters to send to the method. Since the toString method doesn't take any parameters, we pass nil to this function.

Additional note: The CallObjectMethodA only takes 3 arguments in the code above. This is because we are using the TJNIEnv class to wrap the raw interface. Thus, the env parameter does not need
to be passed because it is part of the class. When the class in turn calls the JNI function GetMethodID, it will at that time pass env as a parameter.

There is an assumption in this Delphi function in that it expects that each object in the array is the same kind of object. If you look closely, you'll notice that the toString method is onlyretrieved from one object in
the array. This methodID is used within the loop to print the object. If we wanted to mix different kinds of objects in the array, we would have to retrieve themethodID of the toString method from each object within
the loop, which would add a lot of overhead. (This Delphi procedure assumes that each element in the array is of the same kind.)

The boolean parameter Print is a flag that tells the function whether or not to actually print anything. This flag can be useful when passing very large arrays to the function (for testing purposes.) Also, there is a support function called JStringToString,
which is convenient for converting a Java String to a Delphi string. This function is part of the TJNIEnv class.

Back to top

Concept #5 - Passing a 2-Dimensional Array to Delphi
This examples shows how to pass a 2-D array to Delphi. It is straight-forward to apply this technique to 3-D arrays as well.
(****************************************************************************
*  Java declaration:
*     native public void pass2DByteArray(byte[][] array);
*
*  Pass a 2-D array of Bytes from Java. This method will retrieve each
*  element from the arrays, print it, then multiply it by 10 and store
*  the new Value back into the array. This is to show access/updating
*  of 2-D arrays within native code. The process would be similar for
*  3-D arrays.
*
*  Class:     Native
*  Method:    pass2DByteArray
*  Signature: ([[B)V
*)
procedure Java_Native_pass2DByteArray(PEnv: PJNIEnv; Obj: JObject; Array2D: JObjectArray); {$IFDEF WIN32} stdcall; {$ENDIF} {$IFDEF LINUX} cdecl; {$ENDIF}
var
NumArrays: JSize;
Len: JSize;
I, J: Integer;
JBArray: JByteArray;
Elements, Walker: PJByte;
IsCopy: JBoolean;
JVM: TJNIEnv;
begin
// Create an instance of the Java environment
JVM := TJNIEnv.Create(PEnv);

// Get length of the array (number of arrays)
NumArrays := JVM.GetArrayLength(Array2D);

Writeln('In native method printing 2-D byte array.');
Writeln('Each element is then multiplied by 10 and updated.');

// Loop over each array
for I := 0 to NumArrays - 1 do
begin

// Get the object at the i'th position (it's an array of Bytes)
JBArray := JVM.GetObjectArrayElement(Array2D, i);

// Get the length of this array of Bytes
Len := JVM.GetArrayLength(JBArray);

// Get the elements from the Byte array
Elements := JVM.GetByteArrayElements(JBArray, IsCopy);

// We will "walk" the array with this pointer
Walker := Elements;

// Print each element, then multiply the Value by 10
// and put it back into the array. (This is to prove that
// the array elements have changed when this function returns
// to Java code.
for J := 0 to Len - 1 do
begin
Writeln(Format('%d,%d = %d', [I, J, Walker^]));

// Update the element (just multiply it by 10)
Walker^ := Walker^ * 10;
Inc(Walker);
end;

// Important! From Sun's documentation:
// Since the returned array may be a copy of the Java array, changes made
// to the returned array will not necessarily be reflected in the original
// array until Release<PrimitiveType>ArrayElements() is called.
JVM.ReleaseByteArrayElements(JBArray, Elements, 0);

end;

JVM.Free;
end;


Notes:

The important idea here to understand is that a 2-dimensional array is simply an array of arrays. Because of this, the code above uses a nested loop to access each element of each array. For a 3D array, you would nest another loop.

Back to top

Concept #6 - Calling a Method of a Java Object from Delphi
(See Concept #4, Passing an Array of Objects to Delphi for the example.)
Back to top

Concept #7 - Creating Java Objects within Delphi
The example shows how to create a Java object within the Delphi DLL. An array of Java objects is created and then passed back to Java. The objects created are rectangles (java/awt/Rectangle) and we don't do anything special
with them. Each Rectangle is simply created and initialized to an arbitrary size. Then, they are passed back to Java where they are printed out (using the object'stoString method) for verification.
(****************************************************************************
*  Java declaration:
*     native public jobjectarray returnRectArray(int size);
*
*  This function creates an array of 'size' Rectangles and returns them
*  to the Java caller. First, the array is created, then each element
*  is assigned a Rectangle object.
*
*  Class:     Native
*  Method:    returnRectArray
*  Signature: (I)[Ljava/awt/Rectangle;
*)
function Java_Native_returnRectArray(PEnv: PJNIEnv; Obj: JObject; Size: JInt): JObjectArray; stdcall;
var
Cls: JClass;
Mid: JMethodID;
Element: JObject;
JOArray: JObjectArray;
I: Integer;
Args: array[0..3] of JValue;
JVM: TJNIEnv;
begin
JVM := TJNIEnv.Create(PEnv);

// Find the Rectangle class
Cls := JVM.FindClass('java/awt/Rectangle');
if Cls = nil then begin
WriteLn('Can''t FindClass(java/awt/Rectangle');
Result := nil;
exit;
end;

// Get its constructor (the one that takes 4 integers)
Mid := JVM.GetMethodID(Cls, '<init>', '(IIII)V');
if Mid = nil then begin
WriteLn('Can''t get MethodID for Rectangle constructor');
Result := nil;
exit;
end;

// Allocate the array of Rectangles
JOArray := JVM.NewObjectArray(Size, Cls, nil);

// Now initialize each one to a Rectangle
for I := 0 to Size - 1 do begin

// Create a new Rectangle object
Args[0].i := 0;
Args[1].i := 0;
Args[2].i := 5 * I;
Args[3].i := 10 * I;
Element := JVM.NewObjectA(Cls, Mid, @args);

// Assign the Rectangle to an element of the array
JVM.SetObjectArrayElement(JOArray, I, Element);

end;

// Return the array back to the Java client
Result := JOArray;

JVM.Free;
end;


Notes:

Notice how Delphi calls the constructor for a Rectangle object:
// Get its constructor (the one that takes 4 integers)
mid := env.GetMethodID(env, cls, '<init>', '(IIII)V');

The name of the constructor is <init>. In Java, a constructor's name is the same as its class, but when invoking a constructor from native code, you use the name <init>. You still must specify the signature,
though, because a class can have more than one constructor and we must indicate which constructor we want.

Back to top

Concept #8 - Accessing Fields of a Java Object from Delphi
This example shows how to access fields (members) of the Java object/class. There are four fields declared in the class, each demonstrates a particular point:

public String w - a public object, demonstrates accessing an object.
public int x - a public int, demonstrates accessing a primitive type.
private int y - a private int, demonstrates accessing a private field. (Yes!)
public static int z - a public static int, demonstrates accessing a static field. (class member)

(****************************************************************************
*  Java declaration:
*     native public void printWXYZ();
*
*  Prints out four members of the Native object, w, x, y, and z. This
*  function acts sort of like the traditional 'toString' method in Java.
*  The members are declared in Native.java as such:
*
*  public String w;       // public Object
*  public int x;          // public
*  private int y;         // private (no protection here)
*  public static int z;   // public static
*
*  The return Value from each GetFieldID call should be checked.
*  I don't check because I'm trying to keep the focus on the calls.
*
*  Class:     Native
*  Method:    printWXYZ
*  Signature: ()V
*)
procedure Java_Native_printWXYZ(PEnv: PJNIEnv; Obj: JObject); stdcall;
var
X, Y, Z: JInt;
W: JString;
FID: JFieldID;
Cls: JClass;
Str: string;
JVM: TJNIEnv;
begin
JVM := TJNIEnv.Create(PEnv);

Cls := JVM.GetObjectClass(Obj);

// w is a String
FID := JVM.GetFieldID(Cls, 'w', 'Ljava/lang/String;');

// Get the Object (String) w.
W := JVM.GetObjectField(Obj, FID);

// x is a non-static public field
FID := JVM.GetFieldID(Cls, 'x', 'I');

// Get the int
X := JVM.GetIntField(Obj, FID);

// y is a non-static private field, same as public here
FID := JVM.GetFieldID(Cls, 'y', 'I');

// Get the int
Y := JVM.GetIntField(Obj, FID);

// z is a _static_ public field, so call different method
FID := JVM.GetStaticFieldID(Cls, 'z', 'I');

// Get static int
Z := JVM.GetStaticIntField(Cls, FID);

// Convert Java string into Delphi string
Str := JVM.JStringToString(W);

// Sort of like the traditional 'toString' output
WriteLn(Format('[w = %s, x = %d, y = %d, z = %d]', [Str, X, Y, Z]));

JVM.Free;
end;


Notes:

There is no protection for private fields when accessing them using the JNI. The method for accessing a private field is the same as accessing a public field.
Accessing a static field is slightly different. It requires using one of the GetStaticXXX functions of the JNI, instead of the plain GetXXX functions. This is because a static field is
part of a class and not part of an object.

Back to top

Concept #9 - Handling Java Exceptions within Delphi
This example shows how to detect a Java exception. The code purposely attempts to invoke a non-existent method on a Java object, called nonexistent. This causes a run-time exception that is detected within the Delphi code.
(****************************************************************************
*  Java declaration:
*     native public void handleException();
*
*  Causes an exception in the JVM, but detects it and suppresses it
*
*  Class:     Native
*  Method:    handleException
*  Signature: ()V
*)
procedure Java_Native_handleException(PEnv: PJNIEnv; Obj: JObject); stdcall;
var
Cls: JClass;
AException: JThrowable;
JVM: TJNIEnv;
begin
JVM := TJNIEnv.Create(PEnv);

// Get the class to which this object belongs
Cls := JVM.GetObjectClass(Obj);

// Attempt to get the ID of the 'nonexistent' member, which, not by
// chance, doesn't exist!
JVM.GetFieldID(Cls, 'nonexistent', 'Ljava/lang/String;');

// Check for exception
AException := JVM.ExceptionOccurred;

// exception is non-zero if an exception occurred
if (AException <> nil) then begin

//WriteLn('Exception occurred in Native code and was handled. This was the exception:');
Writeln(Format('Exception handled in Main.cpp: %d', [DWORD(AException)]));

// This call will print out a description of the exception
//JVM.ExceptionDescribe;

// Clear the exception so the JVM doesn't react to it after we handled it
JVM.ExceptionClear;

end;

JVM.Free;
end;


Notes:

You can't use the traditional try..except syntax. You must explicitly check for any exceptions by calling the ExceptionOccurred function of the JNI. In most places throughout this code, I have neglected to do this. This
was to prevent the code from being overwhelmed with exception checks, which would have detracted from the main focus of each example. However, in realcode, you should check for exceptions like the code above.
If you want to print out the exception error message above, simply remove the comment from the line:
JVM.ExceptionDescribe;


Be sure to call ExceptionClear after handling an exception. This will prevent the exception from being thrown again within the JVM upon returning from the function.

Back to top

Concept #10 - Causing Java Exceptions within Delphi

(****************************************************************************
*  Java declaration:
*     native public void causeException();
*
*  Causes an exception in the JVM, but fails to catch it. Thus, it is
*  propagated back to the JVM.
*
*  Class:     Native
*  Method:    causeException
*  Signature: ()V
*)
procedure Java_Native_causeException(PEnv: PJNIEnv; Obj: JObject); stdcall;
var
Cls: JClass;
JVM: TJNIEnv;
begin
JVM := TJNIEnv.Create(PEnv);

// Get the class to which this object belongs
Cls := JVM.GetObjectClass(Obj);

// Attempt to get the ID of the 'nonexistent' member, which, not by
// chance, doesn't exist!
JVM.GetFieldID(Cls, 'nonexistent', 'Ljava/lang/String;');

// An exception has occurred, but it has not been suppressed.
// The JVM will detect it and catch it.
// Because we don't call this: JVM.ExceptionClear,
// the JVM will react.

JVM.Free;
end;

Back to top

TJNIEnv: The Wrapper Class
TJNIEnv is a thin wrapper around the JNI API, allowing it to look and feel a little object oriented. It's called a thin wrapper because it doesn't really hide a lot of the underlying data and functions.
It's only a slight convenience. More importantly, however, is that it is more consistent with the way OO languages, such as Delphi, are coded. Many APIs are simply sets of global functions that operate on the data you provide. The same is true for the Java
Native Interface API.
To get an idea of what the Delphi code would look like without using TJNIEnv, look at the printWXYZ method below. This is a modified version of the method used to describe Concept
#8 above.

procedure Java_Native_printWXYZ(PEnv: PJNIEnv; Obj: JObject); stdcall;
var
X, Y, Z: JInt;
W: JString;
FID: JFieldID;
Cls: JClass;
Str: string;
IsCopy: JBoolean;  // added for GetStringChars
Chars: PJChar;     // added for GetStringChars
begin
// Get the class associated with this object
Cls := PEnv^.GetObjectClass(PEnv, Obj);

// w is a String
FID := PEnv^.GetFieldID(PEnv, Cls, 'w', 'Ljava/lang/String;');

// Get the Object (String) w.
W := PEnv^.GetObjectField(PEnv, Obj, FID);

// x is a non-static public field
FID := PEnv^.GetFieldID(PEnv, Cls, 'x', 'I');

// Get the int
X := PEnv^.GetIntField(PEnv, Obj, FID);

// y is a non-static private field, same as public here
FID := PEnv^.GetFieldID(PEnv, Cls, 'y', 'I');

// Get the int
Y := PEnv^.GetIntField(PEnv, Obj, FID);

// z is a _static_ public field, so call different method
FID := PEnv^.GetStaticFieldID(PEnv, Cls, 'z', 'I');

// Get static int
Z := PEnv^.GetStaticIntField(PEnv, Cls, FID);

// Convert Java string into Delphi string
Chars := PEnv^.GetStringChars(PEnv, W, IsCopy);
Str := string(Chars);
PEnv^.ReleaseStringChars(PEnv, W, Chars);

// Sort of like the traditional 'toString' output
WriteLn(Format('[w = %s, x = %d, y = %d, z = %d]', [Str, X, Y, Z]));
end;

There are a few changes that stand out:

There is no TJNIEnv object (called JVM in all of the examples.) This is simply because we aren't using the wrapped version. Instead, we call each API function through the PJNIEnv parameter that was passed into our method, for
example:
Cls := JVM.GetObjectClass(Obj);

becomes:
Cls := PEnv^.GetObjectClass(PEnv, Obj);


The PJNIEnv parameter that was passed into the method is passed as the first parameter to every API function. When we instantiated a TJNIEnv object, we passed a JNIEnv pointer (PJNIENV) to the constructor:
JVM := TJNIEnv.Create(PEnv);

This pointer is stored in the class and is passed to each API method on behalf of the client. So, this call by the client
Cls := JVM.GetObjectClass(Obj);

calls this function in the TJNIEnv class:
function TJNIEnv.GetObjectClass(Obj: JObject): JClass;
begin
Result := Env^.GetObjectClass(Env, Obj);
end;

Env is the TJNIEnv pointer that was passed into the TJNIEnv constructor above.

Converting the Java string to a Delphi string requires the user to call JNI API functions. The TJNIEnv class hides this from the user by providing a JStringToString function so this (W is the Java string):
Chars := PEnv^.GetStringChars(PEnv, W, IsCopy);
Str := string(Chars);
PEnv^.ReleaseStringChars(PEnv, W, Chars);

becomes this:
Str := JVM.JStringToString(W);


One thing you may be saying to yourself is "I don't see the great benefit of using wrappers around the API" For the TJNIEnv class, I would partially agree. There seems to be almost a one-to-one correspondence between the code you write with the
wrapper class compared to the code you write without it. But don't forget that a major factor in using classes is to be consistent with Delphi's programming style. Also, this allows the TJNIEnv class to be extended at a later time to provide more
functionality than it does now.

In Part Three of Using the Java Native Interface with Delphi you will see another wrapper class called TJavaVM which is not merely a thin
layer, but a full-blown OO interface to the Invocation API of the JNI. Among other things, this class handles loading and unloading of DLLs at the appropriate times, relieving the programmer of this burden.

Back to top

Summary
Part Two of Using the Java Native Interface with
Delphi shows by example how to perform many of the common tasks necessary when using the JNI. These tasks are not specific to Delphi, but are required by
any language that desires to interface with Java using the JNI. These examples show, from a Delphi perspective using Delphi code, how to interact with Java at runtime. After reading this document, you should have a good foundation to further explore Delphi
and the Java Native Interface.

Part Three introduces you to the Invocation API, which is the term given to a subset of the JNI that deals with embedding the Java Virtual Machine into another
program. In our case, this other program will be a Delphi program.

Download the files used in this document.

Using the Java Native Interface with Delphi (Part One)

Using the Java Native Interface with Delphi (Part Three)

Helpful resources (from Part One.)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: