
Professional Java.JDK.5.Edition (Wrox)
.pdf
Chapter 9
Java Native Interface
JNI provides many functions, such as string and array handling, and a complete set of functions to create and use Java objects. These functions all take a pointer to the Java environment as the first parameter. However, in order to simplify programming, these functions all have an alias that is defined in the JNIEnv structure. This means you can invoke any JNI function by calling it through the pointer to the JNI environment. The rest of this chapter describes functions that are defined in this structure instead of the full version that includes this first parameter. Each function’s full declaration will precede its explanation throughout the rest of this chapter.
Data Types
The most important aspect of interfacing with other languages is the treatment of various data types such as strings. Different languages store strings in different ways. For example, character array strings in C and C++ are null-terminated. Strings in Java store the length separately, but are also 0-indexed. JNI provides a number of functions to manipulate strings, described in detail later. The primitive data types are provided with natural analogs on the native side. Consult the following table to see how data types in Java translate to types in C++.
Primitive Type |
Native Type |
Size (Bits) |
|
|
|
boolean |
jboolean |
8, unsigned |
byte |
jbyte |
8 |
char |
jchar |
16, unsigned |
short |
jshort |
16 |
int |
jint |
32 |
long |
jlong |
64 |
float |
jfloat |
32 |
double |
jdouble |
64 |
void |
void |
n/a |
|
|
|
Strings in JNI
The jstring data type is used to handle Java strings in C/C++. This type should not be used directly. If you try to use it in a call to printf (a C function to output text to the screen), for example, you run the risk of crashing the Java virtual machine. The jstring must first be converted to a C-string using one of several string conversion functions that JNI provides.
Java passes strings to the native environment in a slightly modified UTF-8 format. Take a look at Figure 9-3 to see how UTF-8 characters are organized in memory. If the high bit is set for a particular byte, the byte is part of a multibyte character. This means that ASCII characters from value 1 to 127 stay the same,
406

Interacting with C/C++ Using Java Native Interface
and though you can’t count on it, if all the characters in the jstring are in this range, you can use the jstring directly in C/C++ code. Java does not use UTF-8 characters longer than three bytes, and the null character (ASCII 0) is represented by two bytes, not one. This means you will never have a character that has all its bits set to 0.
|
|
|
|
|
|
|
|
|
UTF-8 Character Encoding |
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
A single byte accounts for |
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
characters in the range \u0001 |
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
to \u007F. The high bit is |
|
|
|
|
|
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
always 0. |
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0 |
|
|
|
|
|
Bits 0-6 |
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
(Bit 7) |
|
|
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Two bytes account for |
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
characters in the range\u0080 |
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
to \u07FF, and the null |
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
character, \u0000 |
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
1 |
1 |
0 |
Bits 6-10 |
|
|
|
|
|
1 |
0 |
|
|
Bits 0-5 |
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
High Byte |
|
|
|
|
|
|
|
|
Low Byte |
|
|
||||
|
|
|
|
|
|
|
|
|
|
Three bytes account for |
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
characters in the range \u0800 |
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
to \uFFFF |
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||
1 |
1 |
1 |
|
0 |
|
Bits 12-15 |
|
|
1 |
0 |
Bits 6-11 |
|
1 |
0 |
Bits 0-5 |
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
High Byte |
|
|
|
|
|
|
|
|
|
|
|
Low Byte |
|
Figure 9-3
There are also routines that work with Unicode strings. Unicode characters always take up two bytes. If you’re writing a program that uses localized strings, always handle your strings in Unicode since UTF-8 does not support internationalization. There are five functions that work with UTF-8 strings, and each has a counterpart for Unicode strings. Two additional functions round out the set of string functions. These last two functions obtain a lock or release a lock on the string for purposes of synchronizing strings when in a threaded environment. Each string function takes as its first parameter a pointer to the Java environment. This is already passed in when a native function is called, so this is easily available:
jstring NewString(const jchar *unicodeChars, jsize len);
jstring NewStringUTF(const char *bytes);
The Unicode version of NewString takes a sequence of characters (jchar, which is two bytes) and the length in number of characters (not number of bytes). The UTF version simply takes a sequence of bytes.
407

Chapter 9
Each byte may form part of a one-, twoor three-byte character, and the end of the string is marked by a two-byte NULL character:
jsize GetStringLength(jstring string);
jsize GetStringUTFLength(jstring string);
Both the Unicode and UTF versions of GetStringLength take a jstring and return its size in number of characters:
const jchar *GetStringChars(jstring string, jboolean *isCopy);
const char *GetStringUTFChars(jstring string, jboolean *isCopy);
These two GetStringChars functions return a pointer to the sequence of characters in a specified jstring. These are the main functions used to take a jstring and turn it into a string that can be easily used in native code. The pointer is valid until the accompanying version of ReleaseStringChars is invoked. The Unicode version returns a pointer to jchar, and the UTF returns a pointer to jbyte. The isCopy parameter is set to JNI_TRUE if a copy of the string is made, or set to NULL or JNI_FALSE if no copy is made:
void ReleaseStringChars(jstring string, const jchar *chars);
void ReleaseStringUTFChars(jstring string, const char *utf);
Invoking one of the ReleaseStringChars functions tells the VM that the native code no longer needs to use the character sequence obtained in the call to the accompanying version GetStringChars. The pointer to the characters is no longer valid after this function is called. The original string must be passed in along with the pointer obtained from the GetStringChars call:
void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf);
void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf);
The GetStringRegion functions transfer a substring of the string str to a character buffer. The substring starts at position start and stops at len-1 (therefore transferring len number of characters). This may throw a StringIndexOutOfBoundsException:
const jchar *GetStringCritical(jstring string, jboolean *isCopy);
The GetStringCritical function returns a pointer to the characters in the specified string. If necessary, the characters are copied and the function returns with isCopy set to JNI_TRUE. Otherwise, isCopy is NULL or set to JNI_FALSE. After this function is invoked, and up to the point ReleaseStringCritical is invoked, the functions used cannot cause the current thread to block:
void ReleaseStringCritical(jstring string, const jchar *carray);
The ReleaseStringCritical function releases the pointer obtained from the call to GetStringCritical.
String Example
Here’s an example of implementing a string-replace function in native code. The function replaceString takes a source string and replaces a string inside of the source string with another, then returns the new string. The Java code sets up what is needed on the native side:
408

Interacting with C/C++ Using Java Native Interface
public class StringExamples {
public native String replaceString(String sourceString, String strToReplace, String replaceString);
static { System.loadLibrary(“StringLibrary”);
}
public static void main(String args[])
{
StringExamples ex = new StringExamples();
String str1 = “”;
String str2 = “”;
str1 = “Sky Black”;
str2 = ex.replaceString(str1, “Black”, “Blue”); System.out.println(“The string before: “ + str1); System.out.println(“The string after: “ + str2);
}
}
The C++ implementation of the replaceString method, shown next, makes use of the string functions that you just learned:
JNIEXPORT jstring JNICALL Java_StringExamples_replaceString (JNIEnv *env, jobject obj,
jstring _srcString, jstring _strToReplace, jstring _replString)
{
const char *searchStr, *findStr, *replStr, *found; jstring newString = NULL;
int index;
searchStr = env->GetStringUTFChars(_srcString, NULL); findStr = env->GetStringUTFChars(_strToReplace, NULL); replStr = env->GetStringUTFChars(_replString, NULL);
found = strstr(searchStr, findStr);
if(found != NULL) { char *newStringTemp;
index = found - searchStr; newStringTemp =
new char[strlen(searchStr) + strlen(replStr) + 1];
strcpy(newStringTemp, searchStr); newStringTemp[index] = 0; strcat(newStringTemp, replStr);
strcat(newStringTemp, &searchStr[index+strlen(findStr)]);
newString = env->NewStringUTF((const char*)newStringTemp);
409

Chapter 9
}
env->ReleaseStringUTFChars(_srcString, searchStr); env->ReleaseStringUTFChars(_strToReplace, findStr);
env->ReleaseStringUTFChars(_replString, replStr);
return(newString);
}
The GetStringUTFChars function is used to convert the string to a string guaranteed useable in native code. The code within the if clause performs the search and replace, and finally allocates a new UTF string with the affected string. This reference is returned, so it is the only reference not released using ReleaseStringUTFChars.
Arrays in JNI
JNI supports the use of both arrays of primitive types and arrays of objects. Each primitive type has an array type counterpart. These array types are listed in the following table.
Name of Primitive Data Type |
Array Type (For Use in C/C++ Code) |
|
|
boolean |
jbooleanArray |
byte |
jbyteArray |
char |
jcharArray |
short |
jshortArray |
int |
jintArray |
long |
jlongArray |
float |
jfloatArray |
double |
jdoubleArray |
|
|
Much like strings in JNI, arrays cannot be used directly. JNI provides a complete set of functions to access, get information about, create, and synchronize both arrays of objects and arrays of primitive data types. The following is an example of how Java arrays should NOT be used in C/C++:
JNIEXPORT jint JNICALL
int findNumber(JNIEnv *env, jobject obj, jintArray intArray, jint arraySize, jint numberToFind)
{
int i;
for(i=0; i<arraySize; i++) {
if(intArray[i] == numberToFind) { return(i);
}
}
return(-1);
}
410

Interacting with C/C++ Using Java Native Interface
This piece of code does not take into account any of the functions provided by JNI for processing arrays. JNI has a function to get the length of an array, and functions to access the array elements since the elements cannot be accessed directly. If you attempt to compile and execute the above code, it will crash the VM.
Array Functions
This section separates the array functions into those that work with arrays of objects and those that work with arrays of primitive data types. The function GetArrayLength works with any array. This is what the function looks like:
jsize GetArrayLength(jarray array);
The GetArrayLength function returns the length of the array. This is the same value you get when accessing the length property of the array in Java code.
Functions for Arrays of Objects
There are three functions that are provided for working with arrays of Java objects in native code. These are NewObjectArray, GetObjectArrayElement, and SetObjectArrayElement:
jobjectArray NewObjectArray(jsize length, jclass elementClass,
jobject initialElement);
The NewObjectArray function creates a new object array of size length that holds objects of type elementClass. All elements in the array are set to initialElement, thus providing an easy way to initialize the entire array to null (or to another value):
jobject GetObjectArrayElement(jobjectArray array, jsize index);
The GetObjectArrayElement function retrieves an object inside the array at the index specified by index. If the index is out of bounds, an IndexOutOfBoundsException is thrown:
void SetObjectArrayElement(jobjectArray array, jsize index, jobject value);
The SetObjectArrayElement function sets the array element inside array at position index to value. If the index is out of bounds, an IndexOutOfBoundsException is thrown.
Functions for Arrays of Primitive Types
There are five core functions for use with each primitive data type. There is one version of each function for each primitive data type. Because there are so many functions, this section uses an abbreviation for each function. Certain information must be replaced with correct data types. In the following list of functions, the [Type] is replaced with the exact name of a primitive type from the first column in the following table. The [ArrayType] is replaced with the exact name of the array data type from the second column in the table. The [NativeType] is replaced with the exact name of the native data type from column three in the table. For example, to create a new integer array, you use the function NewIntArray that returns jintArray.
411

Chapter 9
Name of Primitive |
Array Type (For Use |
Primitive Type (For Use |
Data Type |
in C/C++ Code) |
in C/C++ Code) |
|
|
|
boolean |
jbooleanArray |
jboolean |
byte |
jbyteArray |
jbyte |
char |
jcharArray |
jchar |
short |
jshortArray |
jshort |
int |
jintArray |
jint |
long |
jlongArray |
jlong |
float |
jfloatArray |
jfloat |
double |
jdoubleArray |
jdouble |
|
|
|
[ArrayType] New[Type]Array(jsize length);
The NewArray function returns a newly created Java array that is length elements in size.
[NativeType] *Get[Type]ArrayElements([ArrayType] array, jboolean *isCopy);
The GetArrayElements function returns a pointer to an array of the native type that corresponds to the Java data type. The parameter isCopy is set to JNI_TRUE if the memory returned is a copy of the array from the Java code, or JNI_FALSE if the memory is not a copy.
void Release[Type]ArrayElements([ArrayType] array, [NativeType] *elems, jint mode);
The ReleaseArrayElements function releases the memory obtained from the call to Get[Type]ArrayElements. If the native array is not a copy, then the mode parameter can be used to optionally copy memory from the native array back to the Java array. The values of mode and their effects are listed in the following table.
Value of Mode |
Description |
|
|
0 |
Copies the memory from the native array to the Java array |
|
and deallocates the memory used by the native array |
JNI_COMMIT |
Copies the memory from the native array to the Java array, but |
|
does NOT deallocate the memory used by the native array |
JNI_ABORT |
Does not copy memory from the native array to the Java array. |
|
The memory used by the native array is still deallocated. |
|
|
void Get[Type]ArrayRegion([ArrayType] array, jsize start, jsize len,
[NativeType] *buf);
412

Interacting with C/C++ Using Java Native Interface
The GetArrayRegion function operates much like Get[Type]ArrayElements. However, this is used to copy only a subset of the array. The parameter start specifies the starting index to copy from, and len specifies how many positions in the array to copy into the native array:
void Set[Type]ArrayRegion([ArrayType] array, jsize start, jsize len,
[NativeType] *buf);
The SetArrayRegion is the counterpart to Get[Type]ArrayRegion. This function is used to copy a segment of a native array back to the Java array. Elements are copied directly from the beginning of the native array (index 0) but are copied into the Java array starting at position start and len elements are copied over:
void *GetPrimitiveArrayCritical(jarray array, jboolean *isCopy);
The GetPrimitiveArrayCritical function returns a handle to an array after obtaining a lock on the array. If no lock could be established, the isCopy parameter comes back with a value JNI_TRUE. Otherwise, isCopy comes back NULL or as JNI_FALSE:
void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode);
The ReleasePrimitiveArrayCritical releases the array previously returned from a call to GetPrimitiveArrayCritical. Look at the next table to see how the mode parameter affects the array and carray parameters.
Value for Mode |
Meaning |
|
|
0 |
Copies the values from carray into array and frees the mem- |
|
ory associated with carray |
JNI_COMMIT |
Copies the values from carray into array but does not free |
|
the memory associated with carray |
JNI_ABORT |
Does not copy the values from carray to array, but does free |
|
the memory associated with carray |
|
|
Array Examples
Here’s an example of implementing a sort routine in native code. In order to keep things simple, the insertion sort is used. The Java code, as usual, is fairly simple. The native method is declared, then the library is statically loaded, and the native method is invoked in the main method:
public class PrimitiveArrayExample {
public native boolean sortIntArray(int[] numbers);
static { System.loadLibrary(“PrimitiveArrayLibrary”);
}
public static void main(String args[])
{
PrimitiveArrayExample pae = new PrimitiveArrayExample();
413

Chapter 9
int numberList[] = {4, 1, 2, 20, 11, 7, 2};
if(pae.sortIntArray(numberList)) { System.out.print(“The sorted numbers are: “); for(int i=0; i<numberList.length; i++) {
System.out.print(numberList[i] + “ “);
}
System.out.println(); } else {
System.out.println(“The sort operation failed because “ +
“the array memory could not be allocated.”);
}
}
}
The native code uses the array functions to work with an array of integers:
JNIEXPORT jboolean JNICALL Java_PrimitiveArrayExample_sortIntArray (JNIEnv *env, jobject obj, jintArray intArrayToSort)
{
jint *intArray; jboolean isCopy; int i, j, num;
intArray = env->GetIntArrayElements(intArrayToSort, &isCopy);
if(intArray == NULL) { return(false);
}
for(i=1; i<env->GetArrayLength(intArrayToSort); i++) { num = intArray[i];
for(j=i-1; j >= 0 && (intArray[j] > num); j--) { intArray[j+1] = intArray[j];
}
intArray[j+1] = num;
}
env->ReleaseIntArrayElements(intArrayToSort, intArray, 0); return(true);
}
This sortIntArray function uses the GetIntArrayElements in order to work with the array in a native form. The GetArrayLength function is used to know how many elements are in the array, and finally, ReleaseIntArrayElements is used to both save the changed memory to the Java array and deallocate the memory.
As one final example of arrays, create an array of strings and then implement a find function that returns the index to the string:
414

Interacting with C/C++ Using Java Native Interface
public class ObjectArrayExample {
public native int findString(String[] stringList, String stringToFind);
static { System.loadLibrary(“ObjectArrayLibrary”);
}
public static void main(String args[])
{
ObjectArrayExample oae = new ObjectArrayExample(); String[] colors = {“red”,”blue”,”black”,”green”,”grey”}; int foundIndex;
System.out.println(“Searching for ‘black’...”); foundIndex = oae.findString(colors, “black”);
if(foundIndex != -1) {
System.out.println(“The color ‘black’ was found at index “ + foundIndex);
} else {
System.out.println(“The color ‘black’ was not found”);
}
}
}
An array of strings is created and passed to the native method findString. If the string is not found, the method returns -1 and otherwise returns the index to the string from the array:
JNIEXPORT jint JNICALL Java_ObjectArrayExample_findString
(JNIEnv *env, jobject obj, jobjectArray strList, jstring strToFind)
{
const char *findStr; jint i;
int arrayLen;
arrayLen = env->GetArrayLength(strList);
findStr = env->GetStringUTFChars(strToFind, NULL);
if(findStr == NULL) { return(-1);
}
for(i=0; i<arrayLen; i++) {
jstring strElem = (jstring)env->GetObjectArrayElement(strList, i);
if(strElem != NULL) {
const char *strTemp = env->GetStringUTFChars(strElem, NULL);
if(strcmp(strTemp, findStr) == 0) { env->ReleaseStringUTFChars(strElem, strTemp); env->ReleaseStringUTFChars(strToFind, findStr); env->DeleteLocalRef(strElem);
415