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

Professional Java.JDK.5.Edition (Wrox)

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

Chapter 9

This function ensures that at least capacity number of local references can be created. The VM ensures that at least 16 local references can be created when a native method is called. If you try to create more local references than are available, FatalError is invoked. This function returns 0 on success and a negative number on failure along with throwing an OutOfMemoryException:

jint PushLocalFrame(jint capacity);

The PushLocalFrame is a useful function to create a new scope of local references. This makes it simple to release all local references allocated in this frame by using the PopLocalFrame function. When this is called, at least capacity number of local references can be created in this frame. This function returns 0 on success and a negative number on failure along with throwing an OutOfMemoryException:

jobject PopLocalFrame(jobject result);

The PopLocalFrame function releases all local references in the current frame (pops up a level). Since storing the result of this function (the return value) might cause a local reference creation in the about-to- be-popped frame, this function accepts a parameter that causes the reference creation to happen in the topmost frame after the current one is popped. This ensures you maintain a reference that stores the result of this function.

Here’s an example showing the usage of the local reference management functions:

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

{

jint count;

//Let’s figure out just how many local references

//we can create...

for(count=16; count<10000; count++) { if(env->EnsureLocalCapacity(count+1)) {

break;

}

}

printf(“I can create up to %d local references\n”, count);

// Now let’s create a few...

jcharArray charArray; jintArray intArray; jstring str;

str = env->NewStringUTF(“This is a test”);

if(env->PushLocalFrame(10)) { charArray = env->NewCharArray(13);

if(charArray == NULL) {

printf(“Failed to create character array\n”);

426

Interacting with C/C++ Using Java Native Interface

return;

}

if(env->PushLocalFrame(10)) { intArray = env->NewIntArray(14);

if(intArray == NULL) {

printf(“Failed to create integer array\n”); return;

}

//intArray created. Use PopLocalFrame to free all allocated

//references in this scope level, in this case just intArray env->PopLocalFrame(NULL);

}

//charArray created. Use PopLocalFrame to free all allocated

//references in this scope level, in this case just charArray env->PopLocalFrame(NULL);

}

// ‘str’ is freed after this function exits

}

When I ran this function, it printed that it can allocate 4,096 local references. The Java VM only guarantees 16 local references, so always call the EnsureLocalCapacity function if you need a large number of local references. Each call to PushLocalFrame allocates a new scope level for allocating local references. All local references that are allocated are automatically freed when PopLocalFrame is called. Only intArray is freed when the first PopLocalFrame is called, and only charArray is freed when the second call to PopLocalFrame happens.

Global and Weak Global References

Global references are meant for use across different invocations of a native method. They are created only by using the NewGlobalRef function. Global references can also be used across separate threads. Since global references give you these added benefits, there is a small trade-off: Java cannot control the lifetime of a global reference. You must determine when the global reference is no longer needed and deallocate it manually using the DeleteGlobalRef function. Weak global references are much like global references, but the underlying object might be garbage collected at any time. JNI provides a special invocation of IsSameObject for finding out if the underlying object is still valid.

The following functions are used for creating and destroying global references:

jobject NewGlobalRef(jobject lobj);

jweak NewWeakGlobalRef(jobject obj);

NewGlobalRef creates a new global reference and returns it, and NewWeakGlobalRef creates and returns a new weak global reference. The parameter to these functions is the class of the object to create. If you don’t have a handle to a class, you can obtain one by invoking the FindClass function. If you try

427

Chapter 9

to create a reference to the null object, or the object cannot be created, these functions return NULL. If the reference cannot be created due to no more available memory, an OutOfMemoryException is thrown:

void DeleteGlobalRef(jobject gref);

void DeleteWeakGlobalRef(jweak ref);

The DeleteGlobalRef/DeleteWeakGlobalRef functions deallocate the global (or weak global) reference that was previously allocated in a call to NewGlobalRef or NewWeakGlobalRef.

Here’s an example of how to cache a class for use across multiple calls to this native function:

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

{

static jstring globalString = NULL; const char *gStr;

if(globalString == NULL) {

// First time through, create global reference jstring localStr;

localStr = env->NewStringUTF(“This is a string”);

if(localStr == NULL) { return;

}

printf(“Global reference does not exist, creating...\n”); globalString = (jstring)env->NewGlobalRef(localStr);

}

gStr = env->GetStringUTFChars(globalString, NULL);

printf(“The contents of globalString: %s\n”, gStr); fflush(stdout);

env->ReleaseStringUTFChars(globalString, gStr);

}

The globalString is marked static so it is preserved across multiple calls to the function. The globalString reference must be created using NewGlobalRef so that the underlying object is also preserved across multiple calls to this function. The first time this is invoked, a local reference to a string is created. This local reference is then used to create a global reference, which is then stored in globalString. The output from the above function, invoked twice, shows how the globalString is created only the first time through:

--- FIRST TIME CALLING ---

Global reference does not exist, creating...

The contents of globalString: This is a string

--- SECOND TIME CALLING ---

The contents of globalString: This is a string

428

Interacting with C/C++ Using Java Native Interface

Don’t forget to build in code to deallocate the global reference. This example shows only how to create a global reference. When to call DeleteGlobalRef depends on your application design.

Comparing References

JNI provides a special function, IsSameObject, in order to test whether the object behind two references is the same. In C++, the keyword NULL corresponds to a null object in Java. Thus, you can pass NULL as a parameter to IsSameObject or compare an object reference directly to NULL. The IsSameObject function has the following prototype:

jboolean IsSameObject(jobject obj1, jobject obj2);

The IsSameObject function returns JNI_TRUE if the objects are the same, and JNI_FALSE otherwise. If you attempt to compare a weak global reference to NULL using IsSameObject, it returns JNI_TRUE if the underlying object hasn’t been garbage collected, and JNI_FALSE if the object has.

Advanced Programming Using JNI

JNI provides several other capabilities to the programmer of native routines. Since Java is a multithreaded environment, routines related to threading are available on the native side. JNI also supports a way of exposing native routines to Java code singly, rather than making all native functions immediately available through a call to System.load or System.loadLibrary. In addition to these features, Java exposes the reflection library natively.

Java Threading

Since Java is a multithreaded environment, it is possible that one or more threads in a system will invoke native methods. This makes it important to know how native methods and things like global references in native libraries relate to threading in Java. The pointer to the Java environment is thread specific, so don’t use one thread’s environment pointer in another thread. If you plan to pass a local reference from one thread to another, convert it to a global reference first. Local references are also thread specific.

Thread Synchronization

JNI provides two native functions for synchronizing objects, MonitorEnter and MonitorExit. These are the only threading functions that are exposed directly at the native level since these are time-critical functions. Other functions such as wait and notify should be invoked using the method invocation functions described in an earlier section:

jint MonitorEnter(jobject obj);

Invoking the MonitorEnter function is equivalent to using synchronized(obj) in Java. The current thread enters the specified object’s monitor, unless another thread has a lock on the object, in which case the current thread pauses until the other thread releases the object’s monitor. If the current thread already has a lock on the object’s monitor, a counter is incremented for each call to this function for the object. Returns a 0 on success, or a negative value if the function failed:

jint MonitorExit(jobject obj);

429

Chapter 9

The MonitorExit function decrements the object’s monitor counter by 1, or releases the current thread’s lock on the object if the counter reaches 0. Returns a 0 on success, or a negative value if the function failed.

Native NIO Support

Introduced to JNI in the 1.4 version of Java are three functions that work with NIO direct buffers. A direct byte buffer is a container for byte data that Java will do its best to perform native I/O operations on. JNI defines three functions for use with NIO:

jobject NewDirectByteBuffer(void* address, jlong capacity);

Based on a pointer to a memory address and the length of the memory (capacity), this function allocates and returns a new java.nio.ByteBuffer. Returns NULL if this function is not implemented for the current Java virtual machine, or if an exception is thrown. If no memory is available, an OutOfMemoryException is thrown:

void *GetDirectBufferAddress(jobject buf);

The GetDirectBufferAddress function returns a pointer to the address referred to by the java.nio.ByteBuffer object that is passed in. Returns NULL if the function is not implemented, if the buf is not an object of the java.nio.ByteBuffer type, or if the memory region is not defined:

jlong GetDirectBufferCapacity(jobject buf);

The GetDirectBufferCapacity function returns the capacity (in number of bytes) of a java.nio.ByteBuffer object that is passed in. Returns -1 if the function is not implemented or if the buf is not an object of the java.nio.ByteBuffer type.

Manually Registering Native Methods

JNI provides a way to register native methods at run time. This dynamic registration is especially useful when a native application initiates an instance of the virtual machine at run time. Native methods in this application cannot be loaded by the VM (since they aren’t in a native library), but can still be used by the Java code after the functions have been manually registered. It is also possible to register a native function multiple times, changing its implementation at run time. The only requirement for native functions is that they follow the JNICALL calling convention. In this section you will see how to utilize these functions to perform more sophisticated coding tasks using JNI:

jint RegisterNatives(jclass clazz, const JNINativeMethod *methods,

jint nMethods);

The RegisterNatives function is used to register one or more native methods. It returns 0 if successful, or a negative value otherwise. The parameter clazz is a handle to the Java class that contains the native methods about to be registered. The nMethods parameter specifies how many native methods are in the list to register. The methods parameter is a pointer to a list of native methods (can be one or more methods). Each element of the methods array is an instance of the JNINativeMethod structure. The JNINativeMethod structure is as follows:

430

Interacting with C/C++ Using Java Native Interface

typedef struct { char *name; char *signature; void *fnPtr;

} JNINativeMethod;

The strings are UTF-8 encoded strings. The name member contains the name of the native method to register (from the Java class) and signature is the method descriptor that fully describes the method’s type. The fnPtr member is a function pointer that points to the C function to register. The function behind this pointer must adhere to the following prototype:

[ReturnType] (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);

The [ReturnType] must be one of the native equivalents of the Java data types. The first two parameters to all native method implementations are a pointer to the Java environment and a reference to the class/object invoking the native method. The variable argument list is for the regular parameters passed to the method:

jint UnregisterNatives(jclass clazz);

The UnregisterNatives function should not be used except in highly specialized situations. This function unregisters all native methods registered with the class passed in. This function returns 0 on success and a negative value otherwise.

Here’s an example of manually registering a native method. The Java code defines two native functions, one that is used to select which sort routine to use, and the other to perform the sort. The sortNumbers method has no implementation when the library is loaded. The setSort function uses an input parameter to know which sort routine to manually register:

import java.io.*;

public class RegisterNativeExample {

public native boolean sortNumbers(int strList[]); public native void setSort(int whichSort);

static { System.loadLibrary(“RegisterNativeLibrary”);

}

public static void main(String args[])

{

RegisterNativeExample rne = new RegisterNativeExample(); int sortType = 1;

int nums[] = {23, 1, 6, 1, 2, 7, 3, 4};

try {

BufferedReader br = new BufferedReader(

new InputStreamReader(System.in));

System.out.println(“Choose a sort routine”);

System.out.println(“ 1. Bubble”);

System.out.println(“ 2. Insertion”);

431

Chapter 9

System.out.print(“% “);

sortType = Integer.parseInt(br.readLine()); rne.setSort(sortType); rne.sortNumbers(nums); System.out.print(“Sorted numbers are: “); for(int i=0; i<nums.length; i++) {

System.out.print(nums[i] + “ “);

}

System.out.println(“”); } catch(IOException ioe) {

System.out.println(“IOException occurred”); ioe.printStackTrace();

}

}

}

Much like the example of using primitive arrays, the list of numbers is hard-coded. The user is asked to choose which sort to use, and the setSort function manually registers the chosen sort routine.

Here’s the native code. The sort routines are what you would expect, so just their signatures are listed here, along with the setSort function. The full code is available online:

jboolean JNICALL bubbleSort(JNIEnv *env, jobject obj, jintArray intArrayToSort) { /* ... */ }

jboolean JNICALL insertionSort(JNIEnv *env, jobject obj, jintArray intArrayToSort) { /* ... */ }

JNIEXPORT void JNICALL Java_RegisterNativeExample_setSort (JNIEnv *env, jobject obj, jint which)

{

JNINativeMethod sortMethod;

sortMethod.name = “sortNumbers”; sortMethod.signature = “([I)Z”;

if(which == 1) {

sortMethod.fnPtr = bubbleSort; } else {

sortMethod.fnPtr = insertionSort;

}

env->RegisterNatives(env->GetObjectClass(obj), &sortMethod, 1);

}

The name of the sort method in the Java code is sortNumbers and its signature is ([I)Z; that is, it takes an array of integers and returns a boolean. The final member of the JNINativeMethod structure is the function pointer and is set to either bubbleSort or insertionSort. Finally the RegisterNatives function is called to register the single method that was just configured. After this call, the sortNumbers method can be invoked in the Java code.

Reflection

JNI provides a set of reflection functions that mirror those in the Java API. Using these functions makes it possible to discover information about classes such as a class’s super-class or whether one type can be

432

Interacting with C/C++ Using Java Native Interface

cast to another. Functions are also provided to convert jmethodID and jfieldID types to and from their corresponding method or field:

jclass FindClass(const char *name);

The FindClass function searches all classes/jar files found in the CLASSPATH for the class name passed in. If the class is found, a handle to that class is returned. The name is a UTF-8 string that includes the full package name and class name, but the dots are replaced with forward slashes. If the class is not found, NULL is returned and one of the following exceptions are thrown:

ClassFormatError. The class requested is not a valid class.

ClassCircularityError. Tthe class/interface is its own super-class/superinterface.

OutOfMemoryError. There is no memory for the handle to the class.

jclass GetObjectClass(jobject obj);

The GetObjectClass function returns a handle to the class of the object passed in:

jclass GetSuperclass(jclass sub);

The GetSuperclass function returns a handle to the super-class of the class passed in. If java.lang.Object is passed in, or an interface is passed in, this function returns NULL:

jboolean IsAssignableFrom(jclass sub, jclass sup);

The IsAssignableFrom function is used to determine if an object of the class described by sub can be successfully cast to the class described by sup. Returns JNI_TRUE if sub and sup are the same classes, sub is a subclass of sup, or sub implements the interface sup. Returns JNI_FALSE otherwise:

jboolean IsInstanceOf(jobject obj, jclass clazz);

The IsInstanceOf function returns JNI_TRUE if obj is an instance of clazz, and JNI_FALSE otherwise. Passing in NULL for obj causes the function to always return JNI_TRUE since null objects can be cast to any class:

jmethodID FromReflectedMethod(jobject method);

The FromReflectedMethod function accepts a handle to an object of the java.lang.reflect.Method and returns a jmethodID suitable for use in the functions that require a jmethodID:

jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic);

The ToReflectedMethod function accepts a handle to a Java class and a handle to a specific method (which might be a constructor) and returns a java.lang.reflect.Method object corresponding to that method. Set isStatic to JNI_TRUE if the method is a static method, and JNI_FALSE (or 0) otherwise. If the function fails, it returns NULL and throws an OutOfMemoryException:

jfieldID FromReflectedField(jobject field);

433

Chapter 9

The FromReflectedField function accepts a handle to an object of the java.lang.reflect.Field and returns a jfieldID suitable for use in the functions that require a jfieldID:

jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic);

The ToReflectedField function accepts a handle to a Java class and a handle to a specific field and returns a java.lang.reflect.Field object corresponding to that field. Set isStatic to JNI_TRUE if the field is a static field, and JNI_FALSE (or 0) otherwise. If the function fails, it returns NULL and throws an OutOfMemoryException.

Developing an E-Mail Client

To wrap up this chapter, look at a larger program that will retrieve information stored in MS Outlook. This example is based on a project I worked on in the past and provides a way to bring different aspects of JNI together to show what a real-world application of JNI might look like. The e-mail client will provide a user interface to check mail and send mail. This is displayed in a Swing user interface. The mail and mail folder information is accessed using the MAPI routines through COM. The JNI portion is the most important, so the complete user interface is not included here (but is available in the code online for this chapter). In order to retrieve e-mail on the client side, CDO (Collaborative Data Objects) is used, so this example assumes you are running Outlook and it is configured to send mail. Note that due to security updates in Outlook, you might be presented with a dialog cautioning you that an external program is attempting to access information from Outlook or attempting to send mail.

System Design

Take a look at Figure 9-4 to see how the Java code relates to the native code. The MailClient contains the user interface (using Swing). The MailClient class communicates with the JNIMailBridge, which has the native functions to invoke the send and check e-mail native functions. The native library then uses COM to access the information stored in Outlook.

JNI

 

MAPI (COM)

Outlook

JNIMailBridge

MailLibrary

 

 

(Mail Folders

(Java)

(C++)

 

 

and Mail)

 

 

 

MailClient

(Java)

Figure 9-4

434

Interacting with C/C++ Using Java Native Interface

User Interface

The following two figures, Figures 9-5 and 9-6, are screen shots of the actual mail client. In the first screen shot, the Mail Folders tree contains all the folders beneath the top folder from Outlook. The table in the top right shows all the messages in the Example folder (shown after double-clicking on Example). The bottom right contains the body of the message (shown after double-clicking on a specific message in the table).

Figure 9-5

This second screen shot contains a basic set of fields to address an e-mail, write the e-mail, and send it (after hitting the Send Mail button).

Figure 9-6

435

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