Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional Java.JDK.5.Edition (Wrox)

.pdf
Скачиваний:
39
Добавлен:
29.02.2016
Размер:
12.07 Mб
Скачать

Chapter 9

break;

}

env->ReleaseStringUTFChars(strElem, strTemp); env->DeleteLocalRef(strElem);

}

env->ReleaseStringUTFChars(strToFind, findStr);

}

if(i == arrayLen) { return(-1);

} else { return(i);

}

}

The GetArrayLength function is used to retrieve the length of the object array. The object array is then accessed using the GetObjectArrayElement function to retrieve a specific element. Note that the object is then cast to a jstring in order to get a handle to the array element’s specific type. Also note that since the GetObjectArrayElement function returns a local reference, the reference is freed using DeleteLocalRef. As explained in the local reference section, this call to DeleteLocalRef isn’t necessary here, but it helps to remind you that many native functions return a local reference.

Working with Java Objects in C/C++

Java Native Interface also provides a set of functions to manipulate Java objects (using methods/fields), handle exceptions, and synchronize data for threads. These functions provide greater access to Java objects on the native side, allowing for more sophisticated applications. One way that these functions can be used is to make callbacks to Java methods, perhaps to communicate information. You will see this in action in the mail client example at the end of this chapter.

Accessing Fields in JNI

There are two types of member variables in Java classes — static fields, that belong to classes, and nonstatic fields, that belong to individual objects. In order to gain access to a field, you must pass a field descriptor and the name of the field to GetFieldID or GetStaticFieldID. A field descriptor is one or more characters that fully describe a field’s type. For example, the field int number has as its field descriptor I. Consult the next table for a full list of descriptors for primitive types. The descriptor for an array type is prefixed with the character [ for each dimension of the array. Therefore, the type int[] numbers is described by [I, and int[][] numbers is [[I. For reference types, the fully qualified name of the class is used but the dots are replaced with a forward slash and the descriptor is surrounded by an L at the beginning and a semicolon at the end. For example, the type java.lang.Integer is described by Ljava/lang/Integer.

416

 

 

Interacting with C/C++ Using Java Native Interface

 

 

 

 

Primitive Type

Field Descriptor

 

 

 

 

boolean

Z

 

byte

B

 

char

C

 

short

S

 

int

I

 

long

J

 

float

F

 

double

D

 

 

 

Much like the variety of functions for use with arrays of primitive types, each primitive type has its own Get and Set function for fields. This section also uses the abbreviated version for compactness. The [NativeType] is replaced by a string from the first column of the next table, and [Type] is replaced by the corresponding string from the second column in the table.

Name of Primitive Data Type

Primitive Type (For Use in C/C++ Code)

 

 

boolean

jboolean

byte

jbyte

char

jchar

short

jshort

int

jint

long

jlong

float

jfloat

double

jdouble

 

 

Here are the functions that are provided to access fields inside Java classes:

jfieldID GetFieldID(jclass clazz, const char *name, const char *sig);

The GetFieldID function returns a handle to the specified field for use in the Get and Set functions. The GetObjectClass function (described later) can be used to get a jclass suitable for the first parameter to this function. The name is the name of the field, and the sig parameter is the field descriptor. If this function fails, it returns NULL:

[NativeType] Get[Type]Field(jobject obj, jfieldID fieldID);

417

Chapter 9

The GetField function returns the value of a particular field specified by fieldID that belongs to the Java object obj:

void Set[Type]Field(jobject obj, jfieldID fieldID, [NativeType] val);

The SetField function sets the value of a particular field specified by fieldID that belongs to the Java object obj to the value val:

jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig);

The GetStaticFieldID function works the same as GetFieldID but is used for getting a handle to a static field:

[NativeType] GetStatic[Type]Field(jclass clazz, jfieldID fieldID);

The GetStaticField function returns the value of a static field specified by the fieldID handle and belonging to the class described by clazz:

void SetStatic[Type]Field(jclass clazz, jfieldID fieldID, [NativeType] value);

The SetStaticField function sets the value of a static field specified by the fieldID that belongs to the class described by clazz:

Here’s an example of accessing fields on an object. The Java code defines a Point class and the native code performs some transformation on that point:

class Point {

public int x, y, z;

public String toString()

{

return(“(“ + x + “, “ + y + “, “ + z + “)”);

}

}

public class FieldAccessExample {

public native void transformPoint(Point p);

static { System.loadLibrary(“FieldAccessLibrary”);

}

public static void main(String args[])

{

FieldAccessExample fae = new FieldAccessExample(); Point p1 = new Point();

p1.x = 17; p1.y = 20; p1.z = 10;

System.out.println(“The point before transformation: “ + p1);

418

Interacting with C/C++ Using Java Native Interface

fae.transformPoint(p1);

System.out.println(“The point after transformation: “ + p1);

}

}

The native library is loaded as usual. An instance of the Point class is created and set up, then the native function is called. The native code accesses the fields in the Point object and modifies these fields. Note that the object passed in isn’t a copy — any changes done to it in native code take effect in the Java code when the native function returns:

JNIEXPORT void JNICALL Java_FieldAccessExample_transformPoint (JNIEnv *env, jobject obj, jobject thePoint)

{

jfieldID x_id, y_id, z_id; jint x_value, y_value, z_value; jclass cls;

cls = env->GetObjectClass(thePoint);

x_id = env->GetFieldID(cls, “x”, “I”); y_id = env->GetFieldID(cls, “y”, “I”); z_id = env->GetFieldID(cls, “z”, “I”);

x_value = env->GetIntField(thePoint, x_id); y_value = env->GetIntField(thePoint, y_id); z_value = env->GetIntField(thePoint, z_id);

x_value = x_value; y_value = 10*y_value + 5; z_value = 30*z_value + 2;

env->SetIntField(thePoint, x_id, x_value); env->SetIntField(thePoint, y_id, y_value); env->SetIntField(thePoint, z_id, z_value);

}

The GetObjectClass function is used to get a handle to the class behind a specified object. In this case, GetObjectClass returns a handle to the Point class. Each field is an integer, so the field descriptor used is simply I. After the field ID’s are retrieved, accessing the value of the field happens through GetIntField and the field values are written back using SetIntField.

Invoking Java Methods Using JNI

Just like fields, there are static and nonstatic methods in Java. JNI provides functions to execute methods on Java objects and also static methods on Java classes. Much like accessing fields, the name and a descriptor for the method are used in order to get a handle to a specific Java method. Once you have this handle, you pass it to one of the CallMethod functions along with the actual parameters for the method. There are actually a number of CallMethod functions — one for each possible return type from a method. Consult the previous table for a listing of the various return types.

419

Chapter 9

The method descriptor is formed by placing all the method’s parameter types inside a single set of parentheses, and then specifying the return type after the closing parenthesis. Types for parameters and return type use the field descriptor described in the previous section. If the method returns void, the descriptor is simply V. If the method does not take any parameters, the parentheses are left empty. The method descriptor for the main method that you are familiar with is ([Ljava/lang/String;)V. The parameters to main are placed inside the parentheses. The square bracket followed by the String object type corresponds to the data type String []args. Outside the parentheses is a single V since main has void as its return type. If you wish to invoke the constructor, use the method name <init>, and for static constructors, use the name <clinit>.

A shortcut to deriving field and method descriptors can be found in the javap utility that comes with the JDK. By passing the command-line option -s to javap, you get a listing of the descriptors for the methods and fields of a class. For example, running javap on the Point class generates the following output:

H:\CHAPTER9\code>javap -s Point Compiled from FieldAccessExample.java class Point extends java.lang.Object {

public

int x;

 

/*

I

*/

public

int y;

 

/*

I

*/

public

int z;

 

/*

I

*/

Point();

 

/*

()V

*/

public

java.lang.String toString();

/*

()Ljava/lang/String; */

}

Both field descriptors and method descriptors are output. You can copy these descriptors directly into the calls to the GetFieldID or GetMethodID functions instead of figuring the descriptors out manually.

Following is a list of functions for use when invoking methods on Java objects. The various CallMethod functions have versions for each data type, much like the functions for accessing fields, so the abbreviation is also used here. Replace the [NativeType] with a native data type, and replace the [Type] with the type name to finish the name of the function:

jclass GetObjectClass(jobject obj);

The GetObjectClass function returns a jclass that represents the class of the Java object obj that is passed in:

jmethodID GetMethodID(jclass clazz, const char *name, const char *sig);

jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig);

420

Interacting with C/C++ Using Java Native Interface

The GetMethodID and GetStaticMethodID functions return a handle to the specified method for use in the various CallMethod functions. The GetObjectClass function can be used to get a jclass suitable for the first parameter to this function. The name is the name of the method, and the sig parameter is the method descriptor. If this function fails it returns NULL:

[NativeType] Call[Type]Method(jobject obj, jmethodID methodID, ...); [NativeType] Call[Type]MethodV(jobject obj, jmethodID methodID, va_list args);

[NativeType] Call[Type]MethodA(jobject obj, jmethodID methodID, const jvalue *args);

The CallMethod functions (and variants) are used to invoke an instance method on a Java object. The first two parameters to all these functions are a handle to the object that has the method, and the handle to the specific method to invoke. The other parameters are the actual parameters to the Java method about to be invoked. The first function accepts a variable number of arguments and passes these arguments directly to the Java method. The second function accepts the list of arguments as a va_list structure that is prepackaged with the list of arguments. The third function accepts the method arguments as an array of jvalue, which is a union able to take the form of any of the native data type versions of the Java data types, including jobject. If you wish to invoke a constructor or a private method, the method ID has to be obtained based on the actual class of the object, not one of the object’s super-classes:

[NativeType] CallNonvirtual[Type]Method(jobject obj, jclass clazz, jmethodID methodID, ...); [NativeType] CallNonvirtual[Type]MethodV(jobject obj, jclass clazz,

jmethodID methodID, va_list args); [NativeType] CallNonvirtual[Type]MethodA(jobject obj, jclass clazz,

jmethodID methodID, const jvalue *args);

The CallNonvirtual functions also invoke an instance method of an object, but which Java method to invoke is based on the clazz parameter. These enable you to invoke a specific method somewhere in the hierarchy of the object’s class instead of invoking a method based on just the object’s class. Just like the normal CallMethod functions, these allow you to pass in arguments to the Java method in the same three different ways:

[NativeType] CallStatic[Type]Method(jclass clazz, jmethodID methodID, ...); [NativeType] CallStatic[Type]MethodV(jclass clazz, jmethodID methodID,

va_list args);

[NativeType] CallStatic[Type]MethodA(jclass clazz, jmethodID methodID, const jvalue *args);

The CallStaticMethod functions (and variants) invoke a static method belonging to the class clazz that is passed in. Use GetStaticMethodID to obtain a handle to the specific method to invoke. Arguments to the method can be passed in to this function in the same three ways as described above.

Along with showing how to invoke Java methods, the following example shows the relationship of the Call and CallNonvirtual functions to combinations of an object and a handle to a class and a handle to a method:

421

Chapter 9

class InvokeMethodParentClass { public void printMessage()

{

System.out.println(“Inside InvokeMethodParentClass”);

}

}

public class InvokeMethodExample extends InvokeMethodParentClass { public native void execMethods();

static { System.loadLibrary(“InvokeMethodLibrary”);

}

public void printMessage()

{

System.out.println(“Inside InvokeMethodExample”);

}

public static void main(String args[])

{

InvokeMethodExample ime = new InvokeMethodExample();

ime.execMethods();

}

}

The Java source defines a parent and a child class and both classes define the same method. The execMethods native method invokes the Call and CallNonvirtual functions in a variety of ways:

JNIEXPORT void JNICALL Java_InvokeMethodExample_execMethods (JNIEnv *env, jobject obj)

{

jclass childClass, parentClass;

jmethodID parent_methodID, child_methodID;

childClass = env->GetObjectClass(obj);

parentClass = env->FindClass(“InvokeMethodParentClass”);

if(childClass == NULL || parentClass == NULL) { printf(“Couldn’t obtain handle to parent or child class”); return;

}

parent_methodID = env->GetMethodID(childClass, “printMessage”, “()V”); child_methodID = env->GetMethodID(parentClass, “printMessage”, “()V”);

if(parent_methodID == NULL || child_methodID == NULL) { printf(“Couldn’t obtain handle to parent or child method”); return;

}

// These two calls invoke the method on the child class env->CallVoidMethod(obj, parent_methodID);

422

Interacting with C/C++ Using Java Native Interface

env->CallVoidMethod(obj, child_methodID);

// These two calls invoke the method on the parent class env->CallNonvirtualVoidMethod(obj, childClass, parent_methodID); env->CallNonvirtualVoidMethod(obj, parentClass, parent_methodID);

// These two calls invoke the method on the child class env->CallNonvirtualVoidMethod(obj, childClass, child_methodID); env->CallNonvirtualVoidMethod(obj, parentClass, child_methodID);

}

Here’s the output from this example:

Inside InvokeMethodExample

Inside InvokeMethodExample

Inside InvokeMethodParentClass

Inside InvokeMethodParentClass

Inside InvokeMethodExample

Inside InvokeMethodExample

Using the regular version, CallVoidMethod, the child’s method is always invoked, regardless of which method ID is used (the one for the parent class or the one for the child). The CallNonvirtualVoidMethod must be used to cause the method of the parent class to execute. Note that regardless of which class type is passed in, the determining factor for which method to execute is the method ID that is passed in.

Handling Java Exceptions in Native Code

JNI provides hooks to the Java exception mechanism in order to handle exceptions that are thrown in the course of executing methods that are implemented in Java code, or native methods written to throw Java exceptions. This mechanism has no bearing on standard error handling for regular functions implemented in C/C++. JNI provides a set of functions for checking, analyzing, and otherwise handling Java exceptions in native code. This section explores these functions and shows how to go about handling Java exceptions in native code in order to maintain Java’s approach to exception handling:

jboolean ExceptionCheck();

The ExceptionCheck function returns JNI_TRUE if an exception has been thrown, or JNI_FALSE if one hasn’t:

jthrowable ExceptionOccurred();

The ExceptionOccurred function retrieves a local reference to an exception that is being thrown. The native code or the Java code must handle this exception:

void ExceptionDescribe();

The ExceptionDescribe function prints information about the exception that was just thrown to the standard error output. This information includes a stack trace:

void ExceptionClear();

423

Chapter 9

The ExceptionClear function clears an exception if one was just thrown:

jint Throw(jthrowable obj);

The Throw function throws an exception that has already been created. If the exception was successfully thrown, 0 is returned; otherwise, a negative value is returned:

jint ThrowNew(jclass clazz, const char *msg);

The ThrowNew function creates an exception based on clazz, which should inherit from Throwable, with the exception text specified by msg (in UTF-8 format). If the construction and throwing of the exception is successful, this function returns 0; otherwise, a negative value is returned:

void FatalError(const char *msg);

The FatalError function causes the signaling of a fatal error. A fatal error is only for situations where recovery is not possible. The VM is shut down upon calling this function.

You should always check for exceptions that might occur in the course of executing native code. If an exception is left unhandled, it will cause future calls to most JNI functions to fail. Here’s a simple scenario using the FindClass function to try to find a class that isn’t there and then handle the exception:

JNIEXPORT void JNICALL Java_ExceptionExample_testExceptions (JNIEnv *env, jobject obj)

{

//Try to find a class that isn’t there to trigger an exception env->FindClass(“NoSuchClass”);

//If an exception happened, print it out and then clear it if(env->ExceptionCheck()) {

env->ExceptionDescribe(); env->ExceptionClear();

}

}

The first statement in the function triggers a NoClassDefFoundError exception. When running this native function, the following output is generated:

java.lang.NoClassDefFoundError: NoSuchClass

at ExceptionExample.testExceptions(Native Method)

at ExceptionExample.main(ExceptionExample.java:13) Exception in thread “main”

The exception details are printed, specifying which exception was thrown, extra information (in this case, the name of the class passed to FindClass), and the stack trace showing the method calls up to the native method, where the exception was thrown. The stack trace doesn’t include line numbers in the native code since Java does not have native code line number information immediately available to it.

424

Interacting with C/C++ Using Java Native Interface

Working with Object References in Native Code

JNI provides sets of functions to utilize Java objects in native code, as you’ve seen with strings, arrays, and general objects. This raises an important question that you may have already considered — how are references to objects handled? More specifically, how does the garbage collector handle object references and know when to collect garbage? JNI provides three different types of references:

Local References. For use only in a single native method.

Global References. For use across multiple invocations of native methods.

Weak Global References. Just like global references, but these do not prevent the object from being garbage collected.

Local References

Local references are explicitly created using the NewLocalRef function, though a number of JNI functions return a local reference. These references are intended only for use while a native function executes and disappear when that function returns. Local references should not be cached on the native side (such as in a local static variable) since they are not valid across multiple calls to the native method. As soon as the native function returns, any local references that existed are now eligible for garbage collection. If you want to deallocate the local reference before the function returns, you can explicitly deallocate the local reference using the DeleteLocalRef function. Local references are also only valid in the thread that created them, so don’t try to store a local reference and use it in a different thread.

The following functions are available to explicitly create and destroy local references:

jobject NewLocalRef(jobject ref);

The NewLocalRef function returns a new local reference to the object reference passed in. If NULL is passed in, the function returns NULL:

void DeleteLocalRef(jobject obj);

The DeleteLocalRef function deallocates the local reference that is passed in.

All local references are available for garbage collection when a native function returns. Local references are created by many JNI functions, such as GetStringUTFChars. Most local references are created and cleaned up automatically. Since local references are so common, look at the example in the next section to see an example of explicitly accounting for local references.

Managing Local References

You must be conscious of how many local references are currently in use since many functions return local references. JNI only allows for a set maximum number of local references. Also, if you create references to large objects, you run the risk of exhausting the available memory. The following functions are provided for management of local references:

jint EnsureLocalCapacity(jint capacity);

425

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]