|
Next, we want to consider how to transfer strings to and from native methods. As you know, strings in the Java programming language are sequences of UTF-16 code points whereas C strings are null-terminated sequences of bytes, so strings are quite different in the two languages. The Java Native Interface has two sets of functions for manipulating strings, one that converts Java strings to "modified UTF-8" byte sequences and one that 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 Volume 1, Chapter 12. Recall that the "modified UTF-8" encoding leaves ASCII characters unchanged, but all other Unicode characters are encoded as multibyte sequences.) NOTE
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) { jstring jstr; char greeting[] = "Hello, Native World\n"; jstr = (*env)->NewStringUTF(env, greeting); return jstr; } 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 11-1). 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. Figure 11-1. The env pointerC++ NOTE
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 may get a character pointer into the actual Java string. Because 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, then 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 ReleaseStringUTFChars 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. NOTE
Accessing Java Strings from C Code
Calling sprint in a Native MethodLet us put these functions we just described to work and write a class that calls the C function sprintf. We would like to call the function as shown in Example 11-8. Example 11-8. Printf2Test.java1. class Printf2Test 2. { 3. public static void main(String[] args) 4. { 5. double price = 44.95; 6. double tax = 7.75; 7. double amountDue = price * (1 + tax / 100); 8. 9. String s = Printf2.sprint("Amount due = %8.2f", amountDue); 10. System.out.println(s); 11. } 12. } Example 11-9 shows the class with the native sprint method. Example 11-9. Printf2.java1. class Printf2 2. { 3. public static native String sprint(String format, double x); 4. 5. static 6. { 7. System.loadLibrary("Printf2"); 8. } 9. } Therefore, the C function that formats a floating-point number has the prototype
Example 11-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. Example 11-10. Printf2.c1. #include "Printf2.h" 2. #include <string.h> 3. #include <stdlib.h> 4. #include <float.h> 5. 6. /** 7. @param format a string containing a printf format specifier 8. (such as "%8.2f"). Substrings "%%" are skipped. 9. @return a pointer to the format specifier (skipping the '%') 10. or NULL if there wasn't a unique format specifier 11. */ 12. char* find_format(const char format[]) 13. { 14. char* p; 15. char* q; 16. 17. p = strchr(format, '%'); 18. while (p != NULL && *(p + 1) == '%') /* skip %% */ 19. p = strchr(p + 2, '%'); 20. if (p == NULL) return NULL; 21. /* now check that % is unique */ 22. p++; 23. q = strchr(p, '%'); 24. while (q != NULL && *(q + 1) == '%') /* skip %% */ 25. q = strchr(q + 2, '%'); 26. if (q != NULL) return NULL; /* % not unique */ 27. q = p + strspn(p, " -0+#"); /* skip past flags */ 28. q += strspn(q, "0123456789"); /* skip past field width */ 29. if (*q == '.') { q++; q += strspn(q, "0123456789"); } 30. /* skip past precision */ 31. if (strchr("eEfFgG", *q) == NULL) return NULL; 32. /* not a floating point format */ 33. return p; 34. } 35. 36. JNIEXPORT jstring JNICALL Java_Printf2_sprint(JNIEnv* env, jclass cl, 37. jstring format, jdouble x) 38. { 39. const char* cformat; 40. char* fmt; 41. jstring ret; 42. 43. cformat = (*env)->GetStringUTFChars(env, format, NULL); 44. fmt = find_format(cformat); 45. if (fmt == NULL) 46. ret = format; 47. else 48. { 49. char* cret; 50. int width = atoi(fmt); 51. if (width == 0) width = DBL_DIG + 10; 52. cret = (char*) malloc(strlen(cformat) + width); 53. sprintf(cret, cformat, x); 54. ret = (*env)->NewStringUTF(env, cret); 55. free(cret); 56. } 57. (*env)->ReleaseStringUTFChars(env, format, cformat); 58. return ret; 59. } In this function, we chose to keep the 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 what we simply do is not format the number. We show you later how to make a native method throw an exception. |
|