Next, let’s look at how to transfer strings to and from native methods. Strings are quite different in the two languages: In Java, they are sequences of UTF-16 code points, whereas C strings are null-terminated sequences of bytes. JNI has two sets of functions for manipulating strings: One converts Java strings to “modified UTF-8” byte sequences and another converts them to arrays of UTF-16 values—that is, to jchar arrays. (The UTF-8, “modified UTF-8,” and UTF-16 formats were discussed in Chapter 2. Recall that the UTF-8 and “modified UTF-8” encodings leave ASCII characters unchanged, but all other Unicode characters are encoded as multibyte sequences.)
If your C code already uses Unicode, you’ll want to use the second set of conversion functions. On the other hand, if all your strings are restricted to ASCII characters, you can use the “modified UTF-8” conversion functions.
A native method with a String parameter actually receives a value of an opaque type called jstring. A native method with a return value of type String must return a value of type jstring. JNI functions read and construct these jstring objects. For example, the NewStringUTF function makes a new jstring object out of a char array that contains ASCII characters or, more generally, “modified UTF-8”-encoded byte sequences.
JNI functions have a somewhat odd calling convention. Here is a call to the NewStringUTF function:
JNIEXPORT jstring JNICALL Java_HelloNative_getGreeting(JNIEnv* env, jclass cl)
char greeting = “Hello, Native World\n”;
jstr = (*env)->NewStringUTF(env, greeting);
All calls to JNI functions use the env pointer that is the first argument of every native method. The env pointer is a pointer to a table of function pointers (see Figure 12.2). Therefore, you must prefix every JNI call with (*env)-> to actually dereference the function pointer. Furthermore, env is the first parameter of every JNI function.
The NewStringUTF function lets you construct a new jstring. To read the contents of an existing jstring object, use the GetStringUTFChars function. This function returns a const jbyte* pointer to the “modified UTF-8” characters that describe the character string. Note that a specific virtual machine is free to choose this character encoding for its internal string representation, so you might get a character pointer into the actual Java string. Since Java strings are meant to be immutable, it is very important that you treat the const seriously and do not try to write into this character array. On the other hand, if the virtual machine uses UTF-16 or UTF-32 characters for its internal string representation, this function call allocates a new memory block that will be filled with the “modified UTF-8” equivalents.
The virtual machine must know when you are finished using the string so that it can garbage-collect it. (The garbage collector runs in a separate thread, and it can interrupt the execution of native methods.) For that reason, you must call the ReteaseStringUTFChars function.
Alternatively, you can supply your own buffer to hold the string characters by calling the GetStringRegion or GetStringUTFRegion methods.
Finally, the GetStringUTFLength function returns the number of characters needed for the “modified UTF-8” encoding of the string.
Let us put these functions to work and write a class that calls the C function sprintf. We would like to call the function as shown in Listing 12.8.
Therefore, the C function that formats a floating-point number has the prototype
JNIEXPORT jstring JNICALL Java_Printf2_sprint(JNIEnv* env, jclass cl,
jstring format, jdouble x)
Listing 12.10 shows the code for the C implementation. Note the calls to GetStringUTFChars to read the format argument, NewStringUTF to generate the return value, and ReleaseStringUTFChars to inform the virtual machine that access to the string is no longer required.
In this function, we chose to keep error handling simple. If the format code to print a floating-point number is not of the form %w.pc, where c is one of the characters e, E, f, g, or G, then we simply do not format the number. We’ll show you later how to make a native method throw an exception.
Source: Horstmann Cay S. (2019), Core Java. Volume II – Advanced Features, Pearson; 11th edition.