While a “100% Pure Java” solution is nice in principle, there are situations in which you will want to write (or use) code written in another language. (Such code is usually called native code.)
Particularly in the early days of Java, many people assumed that it would be a good idea to use C or C++ to speed up critical parts of a Java application. However, in practice, this was rarely useful. A presentation at the 1996 JavaOne conference showed this clearly. The implementors of the cryptography library at Sun Microsystems reported that a pure Java platform implementation of their cryptographic functions was more than adequate. It was true that the code was not as fast as a C implementation would have been, but it turned out not to matter. The Java platform implementation was far faster than the network I/O. This turned out to be the real bottleneck.
Of course, there are drawbacks to going native. If a part of your application is written in another language, you must supply a separate native library for every platform you want to support. Code written in C or C++ offers no protection against overwriting memory through invalid pointer usage. It is easy to write native methods that corrupt your program or infect the operating system.
Thus, we suggest using native code only when you need to. In particular, there are three reasons why native code might be the right choice:
Your application requires access to system features or devices that are not accessible through the Java platform.
You have substantial amounts of tested and debugged code in another language, and you know how to port it to all desired target platforms.
You have found, through benchmarking, that the Java code is much slower than the equivalent code in another language.
The Java platform has an API for interoperating with native C code called the Java Native Interface (JNI). We discuss JNI programming in this chapter.
You can also use C++ instead of C to write native methods. There are a few advantages—type checking is slightly stricter, and accessing the JNI functions is a bit more convenient. However, JNI does not support any mapping between Java and C++ classes.
Suppose you have a C function that does something you like and, for one reason or another, you don’t want to bother reimplementing it in Java. For the sake of illustration, we start with a simple C function that prints a greeting.
The Java programming language uses the keyword native
for a native method, and you will obviously need to place a method in a class. The result is shown in Listing 12-1.
The native
keyword alerts the compiler that the method will be defined externally. Of course, native methods will contain no code in the Java programming language, and the method header is followed immediately by a terminating semicolon. Therefore, native method declarations look similar to abstract method declarations.
In this particular example, the native method is also declared as static
. Native methods can be both static and nonstatic. We start with a static method because we do not yet want to deal with parameter passing.
You actually can compile this class, but when you go to use it in a program, then the virtual machine will tell you it doesn’t know how to find greeting
—reporting an UnsatisfiedLinkError
. To implement the native code, write a corresponding C function. You must name that function exactly the way the Java virtual machine expects. Here are the rules:
Use the full Java method name, such as HelloNative.greeting
. If the class is in a package, then prepend the package name, such as com.horstmann.HelloNative.greeting
.
Replace every period with an underscore, and append the prefix Java_
. For example, Java_HelloNative_greeting
or Java_com_horstmann_HelloNative_greeting
.
If the class name contains characters that are not ASCII letters or digits—that is, '_'
, '$'
, or Unicode characters with code greater than 'u007F'
—replace them with _0
xxxx, where xxxx is the sequence of four hexadecimal digits of the character’s Unicode value.
If you overload native methods, that is, if you provide multiple native methods with the same name, then you must append a double underscore followed by the encoded argument types. (We describe the encoding of the argument types later in this chapter.) For example, if you have a native method, greeting
, and another native method, greeting(int repeat)
, then the first one is called Java_HelloNative_greeting__
, and the second, Java_HelloNative_greeting__I
.
Actually, nobody does this by hand; instead, you run the javah
utility, which automatically generates the function names. To use javah
, first, compile the source file in Listing 12-1:
javac HelloNative.java
Next, call the javah
utility, which produces a C header file from the class file. The javah
executable can be found in the jdk/bin
directory. You invoke it with the name of the class, just as you would invoke the Java compiler. For example,
javah HelloNative
This command creates a header file, HelloNative.h
, which is shown in Listing 12-2.
Example 12-2. HelloNative.h
1. /* DO NOT EDIT THIS FILE - it is machine generated */ 2. #include <jni.h> 3. /* Header for class HelloNative */ 4. 5. #ifndef _Included_HelloNative 6. #define _Included_HelloNative 7. #ifdef __cplusplus 8. extern "C" { 9. #endif 10. /* 11. * Class: HelloNative 12. * Method: greeting 13. * Signature: ()V 14. */ 15. JNIEXPORT void JNICALL Java_HelloNative_greeting 16. (JNIEnv *, jclass); 17. 18. #ifdef __cplusplus 19. } 20. #endif 21. #endif
As you can see, this file contains the declaration of a function Java_HelloNative_greeting
. (The macros JNIEXPORT
and JNICALL
are defined in the header file jni.h
. They denote compiler-dependent specifiers for exported functions that come from a dynamically loaded library.)
Now, you simply copy the function prototype from the header file into the source file and give the implementation code for the function, as shown in Listing 12-3.
In this simple function, ignore the env
and cl
arguments. You’ll see their use later.
You can use C++ to implement native methods. However, you must then declare the functions that implement the native methods as extern "C"
. (This stops the C++ compiler from “mangling” the method name.) For example,
extern "C"
JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv* env, jclass cl)
{
cout << "Hello, Native World!" << endl;
}
You compile the native C code into a dynamically loaded library. The details depend on your compiler.
For example, with the Gnu C compiler on Linux, use these commands:
gcc -fPIC -I jdk/include -I jdk/include/linux -shared -o libHelloNative.so HelloNative.c
With the Sun compiler under the Solaris Operating System, the command is
cc -G -I jdk/include -I jdk/include/solaris -o libHelloNative.so HelloNative.c
With the Microsoft compiler under Windows, the command is
cl -I jdkinclude -I jdkincludewin32 -LD HelloNative.c -FeHelloNative.dll
Here, jdk is the directory that contains the JDK.
If you use the Microsoft compiler from a command shell, first run the batch file vcvars32.bat
or vsvars32.bat
. That batch file sets up the path and the environment variables needed by the compiler. You can find it in the directory c:Program FilesMicrosoft Visual Studio .NET 2003Common7 ools
, c:Program FilesMicrosoft Visual Studio 8VC,
or a similar monstrosity.
You can also use the freely available Cygwin programming environment, available from http://www.cygwin.com. It contains the Gnu C compiler and libraries for UNIX-style programming on Windows. With Cygwin, use the command
gcc -mno-cygwin -D __int64="long long" -I jdk/include/ -I jdk/include/win32 -shared -Wl,--add-stdcall-alias -o HelloNative.dll HelloNative.c
Type the entire command on a single line.
The Windows version of the header file jni_md.h
contains the type declaration
typedef __int64 jlong;
which is specific to the Microsoft compiler. If you use the Gnu compiler, you might want to edit that file, for example,
#ifdef __GNUC__ typedef long long jlong; #else typedef __int64 jlong; #endif
Alternatively, compile with -D __int64="long long"
, as shown in the sample compiler invocation.
Finally, add a call to the System.loadLibrary
method in your program. To ensure that the virtual machine will load the library before the first use of the class, use a static initialization block, as in Listing 12-4.
Figure 12-1 gives a summary of the native code processing.
If you compile and run this program, the message “Hello, Native World!” is displayed in a terminal window.
If you run Linux, you must add the current directory to the library path. Either set the LD_LIBRARY_PATH
environment variable,
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
or set the java.library.path
system property:
java -Djava.library.path=. HelloNativeTest
Of course, this is not particularly impressive by itself. However, if you keep in mind that this message is generated by the C printf
command and not by any Java programming language code, you will see that we have taken the first steps toward bridging the gap between the two languages!
In summary, you follow these steps to link a native method to a Java program:
Declare a native method in a Java class.
Run javah
to get a header file with a C declaration for the method.
Implement the native method in C.
Place the code in a shared library.
Load that library in your Java program.
Some shared libraries for native code must run initialization code. You can place any initialization code into a JNI_OnLoad
method. Similarly, when the virtual machine (VM) shuts down, it will call the JNI_OnUnload
method if you provide it. The prototypes are
jint JNI_OnLoad(JavaVM* vm, void* reserved); void JNI_OnUnload(JavaVM* vm, void* reserved);
The JNI_OnLoad
method needs to return the minimum version of the VM that it requires, such as JNI_VERSION_1_2
.
When passing numbers between C and Java, you should understand which types correspond to each other. For example, although C does have data types called int
and long
, their implementation is platform dependent. On some platforms, ints
are 16-bit quantities, and on others they are 32-bit quantities. In the Java platform, of course, an int
is always a 32-bit integer. For that reason, JNI defines types jint
, jlong
, and so on.
Table 12-1 shows the correspondence between Java types and C types.
In the header file jni.h
, these types are declared with typedef
statements as the equivalent types on the target platform. That header file also defines the constants JNI_FALSE = 0
and JNI_TRUE = 1
.
Until Java SE 5.0, Java had no direct analog to the C printf
function. In the following examples, we will suppose you are stuck with an ancient JDK release and decide to implement the same functionality by calling the C printf
function in a native method.
Listing 12-5 shows a class called Printf1
that uses a native method to print a floating-point number with a given field width and precision.
Notice that when the method is implemented in C, all int
and double
parameters are changed to jint
and jdouble
, as shown in Listing 12-6.
Example 12-6. Printf1.c
1. /** 2. @version 1.10 1997-07-01 3. @author Cay Horstmann 4. */ 5. 6. #include "Printf1.h" 7. #include <stdio.h> 8. 9. JNIEXPORT jint JNICALL Java_Printf1_print(JNIEnv* env, jclass cl, 10. jint width, jint precision, jdouble x) 11. { 12. char fmt[30]; 13. jint ret; 14. sprintf(fmt, "%%%d.%df", width, precision); 15. ret = printf(fmt, x); 16. fflush(stdout); 17. return ret; 18. }
The function simply assembles a format string "%w.
p
f"
in the variable fmt
, then calls printf
. It then returns the number of characters printed.
Listing 12-7 shows the test program that demonstrates the Printf1
class.
Example 12-7. Printf1Test.java
1. /** 2. * @version 1.10 1997-07-01 3. * @author Cay Horstmann 4. */ 5. class Printf1Test 6. { 7. public static void main(String[] args) 8. { 9. int count = Printf1.print(8, 4, 3.14); 10. count += Printf1.print(8, 4, count); 11. System.out.println(); 12. for (int i = 0; i < count; i++) 13. System.out.print("-"); 14. System.out.println(); 15. } 16. }
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. JNI 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 I, Chapter 12. Recall that the “modified UTF-8” encoding leaves ASCII characters unchanged, but all other Unicode characters are encoded as multibyte sequences.)
The standard UTF-8 encoding and the “modified UTF-8” encoding differ only for “supplementary” characters with code higher than 0xFFFF. In the standard UTF-8 encoding, these characters are encoded as a 4-byte sequence. However, in the “modified” encoding, the character is first encoded as a pair of “surrogates” in the UTF-16 encoding, and then each surrogate is encoded with UTF-8, yielding a total of 6 bytes. This is clumsy, but it is a historical accident—the JVM specification was written when Unicode was still limited to 16 bits.
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 "; 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 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.
It is simpler to access JNI functions in C++. The C++ version of the JNIEnv
class has inline member functions that take care of the function pointer lookup for you. For example, you can call the NewStringUTF
function as
jstr = env->NewStringUTF(greeting);
Note that you omit the JNIEnv
pointer from the parameter list of the call.
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. 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.
You can find the JNI API at http://java.sun.com/javase/6/docs/technotes/guides/jni/index.html.
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.
Example 12-8. Printf2Test.java
1. /** 2. * @version 1.10 1997-07-01 3. * @author Cay Horstmann 4. */ 5. class Printf2Test 6. { 7. public static void main(String[] args) 8. { 9. double price = 44.95; 10. double tax = 7.75; 11. double amountDue = price * (1 + tax / 100); 12. 13. String s = Printf2.sprint("Amount due = %8.2f", amountDue); 14. System.out.println(s); 15. } 16. }
Listing 12-9 shows the class with the native sprint
method.
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.
Example 12-10. Printf2.c
1. /** 2. @version 1.10 1997-07-01 3. @author Cay Horstmann 4. */ 5. 6. #include "Printf2.h" 7. #include <string.h> 8. #include <stdlib.h> 9. #include <float.h> 10. 11. /** 12. @param format a string containing a printf format specifier 13. (such as "%8.2f"). Substrings "%%" are skipped. 14. @return a pointer to the format specifier (skipping the '%') 15. or NULL if there wasn't a unique format specifier 16. */ 17. char* find_format(const char format[]) 18. { 19. char* p; 20. char* q; 21. 22. p = strchr(p + 2, '%'); 23. if (p == NULL) return NULL; 24. /* now check that % is unique */ 25. p = strchr(format, '%'); 26. while (p != NULL && *(p + 1) == '%') /* skip %% */ 27. p++; 28. q = strchr(p, '%'); 29. while (q != NULL && *(q + 1) == '%') /* skip %% */ 30. q = strchr(q + 2, '%'); 31. if (q != NULL) return NULL; /* % not unique */ 32. q = p + strspn(p, " -0+#"); /* skip past flags */ 33. q += strspn(q, "0123456789"); /* skip past field width */ 34. if (*q == '.') { q++; q += strspn(q, "0123456789"); } 35. /* skip past precision */ 36. if (strchr("eEfFgG", *q) == NULL) return NULL; 37. /* not a floating-point format */ 38. return p; 39. } 40. 41. JNIEXPORT jstring JNICALL Java_Printf2_sprint(JNIEnv* env, jclass cl, 42. jstring format, jdouble x) 43. { 44. const char* cformat; 45. char* fmt; 46. jstring ret; 47. 48. cformat = (*env)->GetStringUTFChars(env, format, NULL); 49. fmt = find_format(cformat); 50. if (fmt == NULL) 51. ret = format; 52. else 53. { 54. char* cret; 55. int width = atoi(fmt); 56. if (width == 0) width = DBL_DIG + 10; 57. cret = (char*) malloc(strlen(cformat) + width); 58. sprintf(cret, cformat, x); 59. ret = (*env)->NewStringUTF(env, cret); 60. free(cret); 61. } 62. (*env)->ReleaseStringUTFChars(env, format, cformat); 63. return ret; 64. }
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 we simply do not format the number. We show you later how to make a native method throw an exception.
All the native methods that you saw so far were static methods with number and string parameters. We next consider native methods that operate on objects. As an exercise, we implement a method of the Employee
class that was introduced in Volume I, Chapter 4, using a native method. Again, this is not something you would normally want to do, but it does illustrate how to access fields from a native method when you need to do so.
To see how to access instance fields from a native method, we will reimplement the raiseSalary
method. Here is the code in Java:
public void raiseSalary(double byPercent) { salary *= 1 + byPercent / 100; }
Let us rewrite this as a native method. Unlike the previous examples of native methods, this is not a static method. Running javah
gives the following prototype:
JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv *, jobject, jdouble);
Note the second argument. It is no longer of type jclass
but of type jobject
. In fact, it is the equivalent of the this
reference. Static methods obtain a reference to the class, whereas nonstatic methods obtain a reference to the implicit this
argument object.
Now we access the salary
field of the implicit argument. In the “raw” Java-to-C binding of Java 1.0, this was easy—a programmer could directly access object data fields. However, direct access requires all virtual machines to expose their internal data layout. For that reason, the JNI requires programmers to get and set the values of data fields by calling special JNI functions.
In our case, we need to use the GetDoubleField
and SetDoubleField
functions because the type of salary
is a double
. There are other functions—GetIntField/SetIntField
, GetObjectField/SetObjectField
, and so on—for other field types. The general syntax is:
x = (*env)->GetXxxField(env, this_obj, fieldID); (*env)->SetXxxField(env, this_obj, fieldID, x);
Here, class
is a value that represents a Java object of type Class
, fieldID
is a value of a special type, jfieldID
, that identifies a field in a structure, and Xxx represents a Java data type (Object
, Boolean
, Byte
, and so on). There are two ways to obtain the class
object. The GetObjectClass
function returns the class of any object. For example:
jclass class_Employee = (*env)->GetObjectClass(env, this_obj);
The FindClass
function lets you specify the class name as a string (curiously, with /
instead of periods as package name separators).
jclass class_String = (*env)->FindClass(env, "java/lang/String");
Use the GetFieldID
function to obtain the fieldID
. You must supply the name of the field and its signature, an encoding of its type. For example, here is the code to obtain the field ID of the salary
field.
jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D");
The string "D"
denotes the type double
. You learn the complete rules for encoding signatures in the next section.
You might be thinking that accessing a data field seems quite convoluted. The designers of the JNI did not want to expose the data fields directly, so they had to supply functions for getting and setting field values. To minimize the cost of these functions, computing the field ID from the field name—which is the most expensive step—is factored out into a separate step. That is, if you repeatedly get and set the value of a particular field, you incur the cost of computing the field identifier only once.
Let us put all the pieces together. The following code reimplements the raiseSalary
method as a native method.
JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv* env, jobject this_obj, jdouble byPercent) { /* get the class */ jclass class_Employee = (*env)->GetObjectClass(env, this_obj); /* get the field ID */ jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D"); /* get the field value */ jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary); salary *= 1 + byPercent / 100; /* set the field value */ (*env)->SetDoubleField(env, this_obj, id_salary, salary); }
Class references are only valid until the native method returns. Thus, you cannot cache the return values of GetObjectClass
in your code. Do not store away a class reference for reuse in a later method call. You must call GetObjectClass
every time the native method executes. If this is intolerable, you can lock the reference with a call to NewGlobalRef
:
static jclass class_X = 0; static jfieldID id_a; . . . if (class_X == 0) { jclass cx = (*env)->GetObjectClass(env, obj); class_X = (*env)->NewGlobalRef(env, cx); id_a = (*env)->GetFieldID(env, cls, "a", ". . ."); }
Now you can use the class reference and field IDs in subsequent calls. When you are done using the class, make sure to call
(*env)->DeleteGlobalRef(env, class_X);
Listings 12-11 and 12-12 show the Java code for a test program and the Employee
class. Listing 12-13 contains the C code for the native raiseSalary
method.
Example 12-11. EmployeeTest.java
1. /** 2. * @version 1.10 1999-11-13 3. * @author Cay Horstmann 4. */ 5. 6. public class EmployeeTest 7. { 8. public static void main(String[] args) 9. { 10. Employee[] staff = new Employee[3]; 11. 12. staff[0] = new Employee("Harry Hacker", 35000); 13. staff[1] = new Employee("Carl Cracker", 75000); 14. staff[2] = new Employee("Tony Tester", 38000); 15. 16. for (Employee e : staff) 17. e.raiseSalary(5); 18. for (Employee e : staff) 19. e.print(); 20. } 21. }
Example 12-12. Employee.java
1. /** 2. * @version 1.10 1999-11-13 3. * @author Cay Horstmann 4. */ 5. 6. public class Employee 7. { 8. public Employee(String n, double s) 9. { 10. name = n; 11. salary = s; 12. } 13. 14. public native void raiseSalary(double byPercent); 15. 16. public void print() 17. { 18. System.out.println(name + " " + salary); 19. } 20. 21. private String name; 22. private double salary; 23. 24. static 25. { 26. System.loadLibrary("Employee"); 27. } 28. }
Example 12-13. Employee.c
1. /** 2. @version 1.10 1999-11-13 3. @author Cay Horstmann 4. */ 5. 6. #include "Employee.h" 7. 8. #include <stdio.h> 9. 10. JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv* env, jobject this_obj, 11. jdouble byPercent) 12. { 13. /* get the class */ 14. jclass class_Employee = (*env)->GetObjectClass(env, this_obj); 15. 16. /* get the field ID */ 17. jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D"); 18. 19. /* get the field value */ 20. jdouble salary = (*env)->GetDoubleField(env, this_obj, id_salary); 21. 22. salary *= 1 + byPercent / 100; 23. 24. /* set the field value */ 25. (*env)->SetDoubleField(env, this_obj, id_salary, salary); 26. }
Accessing static fields is similar to accessing nonstatic fields. You use the GetStaticFieldID
and GetStatic
XxxField/SetStatic
XxxField
functions. They work almost identically to their nonstatic counterpart, with two differences:
Because you have no object, you must use FindClass
instead of GetObjectClass
to obtain the class reference.
You supply the class, not the instance object, when accessing the field.
For example, here is how you can get a reference to System.out
.
/* get the class */ jclass class_System = (*env)->FindClass(env, "java/lang/System"); /* get the field ID */ jfieldID id_out = (*env)->GetStaticFieldID(env, class_System, "out", "Ljava/io/PrintStream;"); /* get the field value */ jobject obj_out = (*env)->GetStaticObjectField(env, class_System, id_out);
To access instance fields and call methods that are defined in the Java programming language, you need to learn the rules for “mangling” the names of data types and method signatures. (A method signature describes the parameters and return type of the method.) Here is the encoding scheme:
|
|
|
|
|
|
|
|
|
|
|
|
| a class type |
|
|
|
|
|
|
To describe an array type, use a [.
For example, an array of strings is
[Ljava/lang/String;
A float[][]
is mangled into
[[F
For the complete signature of a method, you list the parameter types inside a pair of parentheses and then list the return type. For example, a method receiving two integers and returning an integer is encoded as
(II)I
The print method that we used in the preceding example has a mangled signature of
(Ljava/lang/String;)V
That is, the method receives a string and returns void
.
Note that the semicolon at the end of the L
expression is the terminator of the type expression, not a separator between parameters. For example, the constructor
Employee(java.lang.String, double, java.util.Date)
has a signature
"(Ljava/lang/String;DLjava/util/Date;)V"
Note that there is no separator between the D
and Ljava/util/Date;
. Also note that in this encoding scheme, you must use /
instead of .
to separate the package and class names. The V
at the end denotes a return type of void
. Even though you don’t specify a return type for constructors in Java, you need to add a V
to the virtual machine signature.
You can use the javap
command with option -s
to generate the method signatures from class files. For example, run
javap -s -private Employee
You get the following output, displaying the signatures of all fields and methods.
Compiled from "Employee.java" public class Employee extends java.lang.Object{ private java.lang.String name; Signature: Ljava/lang/String; private double salary; Signature: D public Employee(java.lang.String, double); Signature: (Ljava/lang/String;D)V public native void raiseSalary(double); Signature: (D)V public void print(); Signature: ()V static {}; Signature: ()V }
There is no rationale whatsoever for forcing programmers to use this mangling scheme for describing signatures. The designers of the native calling mechanism could have just as easily written a function that reads signatures in the Java programming language style, such as void(int,java.lang.String)
, and encodes them into whatever internal representation they prefer. Then again, using the mangled signatures lets you partake in the mystique of programming close to the virtual machine.
Of course, Java programming language functions can call C functions—that is what native methods are for. Can we go the other way? Why would we want to do this anyway? The answer is that it often happens that a native method needs to request a service from an object that was passed to it. We first show you how to do it for instance methods, and then we show you how to do it for static methods.
As an example of calling a Java method from native code, let’s enhance the Printf
class and add a method that works similarly to the C function fprintf
. That is, it should be able to print a string on an arbitrary PrintWriter
object. Here is the definition of the method in Java:
class Printf3 { public native static void fprint(PrintWriter out, String s, double x); . . . }
We first assemble the string to be printed into a String
object str
, as in the sprint
method that we already implemented. Then, we call the print
method of the PrintWriter
class from the C function that implements the native method.
You can call any Java method from C by using the function call
(*env)->CallXxxMethod(env, implicit parameter, methodID, explicit parameters)
Replace Xxx with Void
, Int
, Object
, and so on, depending on the return type of the method. Just as you need a fieldID
to access a field of an object, you need a method ID to call a method. You obtain a method ID by calling the JNI function GetMethodID
and supplying the class, the name of the method, and the method signature.
In our example, we want to obtain the ID of the print
method of the PrintWriter
class. As you saw in Volume I, Chapter 12, the PrintWriter
class has several overloaded methods called print
. For that reason, you must also supply a string describing the parameters and return the value of the specific function that you want to use. For example, we want to use void print(java.lang.String)
. As described in the preceding section, we must now “mangle” the signature into the string "(Ljava/lang/String;)V"
.
Here is the complete code to make the method call, by
Obtaining the class of the implicit parameter.
Obtaining the method ID.
/* get the class */ class_PrintWriter = (*env)->GetObjectClass(env, out); /* get the method ID */ id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(Ljava/lang/String;)V"); /* call the method */ (*env)->CallVoidMethod(env, out, id_print, str);
Listings 12-14 and 12-15 show the Java code for a test program and the Printf3
class. Listing 12-16 contains the C code for the native fprint
method.
The numerical method IDs and field IDs are conceptually similar to Method
and Field
objects in the reflection API. You can convert between them with the following functions:
jobject ToReflectedMethod(JNIEnv* env, jclass class, jmethodID methodID); // returns Method object methodID FromReflectedMethod(JNIEnv* env, jobject method); jobject ToReflectedField(JNIEnv* env, jclass class, jfieldID fieldID); // returns Field object fieldID FromReflectedField(JNIEnv* env, jobject field);
Calling static methods from native methods is similar to calling instance methods. There are two differences.
You use the GetStaticMethodID
and CallStatic
XxxMethod
functions.
You supply a class object, not an implicit parameter object, when invoking the method.
As an example of this, let’s make the call to the static method
System.getProperty("java.class.path")
from a native method. The return value of this call is a string that gives the current class path.
First, we have to find the class to use. Because we have no object of the class System
readily available, we use FindClass
rather than GetObjectClass
.
jclass class_System = (*env)->FindClass(env, "java/lang/System");
Next, we need the ID of the static getProperty
method. The encoded signature of that method is
"(Ljava/lang/String;)Ljava/lang/String;"
because both the parameter and the return value are a string. Hence, we obtain the method ID as follows:
jmethodID id_getProperty = (*env)->GetStaticMethodID(env, class_System, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
Finally, we can make the call. Note that the class object is passed to the CallStaticObjectMethod
function.
jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, id_getProperty, (*env)->NewStringUTF(env, "java.class.path"));
The return value of this method is of type jobject
. If we want to manipulate it as a string, we must cast it to jstring
:
jstring str_ret = (jstring) obj_ret;
In C, the types jstring
and jclass
, as well as the array types that are introduced later, are all type equivalent to jobject
. The cast of the preceding example is therefore not strictly necessary in C. But in C++, these types are defined as pointers to “dummy classes” that have the correct inheritance hierarchy. For example, the assignment of a jstring
to a jobject
is legal without a cast in C++, but the assignment from a jobject
to a jstring
requires a cast.
A native method can create a new Java object by invoking its constructor. You invoke the constructor by calling the NewObject
function.
jobject obj_new = (*env)->NewObject(env, class, methodID, construction parameters);
You obtain the method ID needed for this call from the GetMethodID
function by specifying the method name as "<init>"
and the encoded signature of the constructor (with return type void
). For example, here is how a native method can create a FileOutputStream
object.
const char[] fileName = ". . ."; jstring str_fileName = (*env)->NewStringUTF(env, fileName); jclass class_FileOutputStream = (*env)->FindClass(env, "java/io/FileOutputStream"); jmethodID id_FileOutputStream = (*env)->GetMethodID(env, class_FileOutputStream, "<init>", "(Ljava/lang/String;)V"); jobject obj_stream = (*env)->NewObject(env, class_FileOutputStream, id_FileOutputStream, str_fileName);
Note that the signature of the constructor takes a parameter of type java.lang.String
and has a return type of void
.
Several variants of the JNI functions call a Java method from native code. These are not as important as the functions that we already discussed, but they are occasionally useful.
The CallNonvirtual
XxxMethod
functions receive an implicit argument, a method ID, a class object (which must correspond to a superclass of the implicit argument), and explicit arguments. The function calls the version of the method in the specified class, bypassing the normal dynamic dispatch mechanism.
All call functions have versions with suffixes “A” and “V” that receive the explicit parameters in an array or a va_list
(as defined in the C header stdarg.h
).
Example 12-14. Printf3Test.java
1. import java.io.*; 2. 3. /** 4. * @version 1.10 1997-07-01 5. * @author Cay Horstmann 6. */ 7. class Printf3Test 8. { 9. public static void main(String[] args) 10. { 11. double price = 44.95; 12. double tax = 7.75; 13. double amountDue = price * (1 + tax / 100); 14. PrintWriter out = new PrintWriter(System.out); 15. Printf3.fprint(out, "Amount due = %8.2f ", amountDue); 16. out.flush(); 17. } 18. }
Example 12-16. Printf3.c
1. /** 2. @version 1.10 1997-07-01 3. @author Cay Horstmann 4. */ 5. 6. #include "Printf3.h" 7. #include <string.h> 8. #include <stdlib.h> 9. #include <float.h> 10. 11. /** 12. @param format a string containing a printf format specifier 13. (such as "%8.2f"). Substrings "%%" are skipped. 14. @return a pointer to the format specifier (skipping the '%') 15. or NULL if there wasn't a unique format specifier 16. */ 17. char* find_format(const char format[]) 18. { 19. char* p; 20. char* q; 21. 22. p = strchr(format, '%'); 23. while (p != NULL && *(p + 1) == '%') /* skip %% */ 24. p = strchr(p + 2, '%'); 25. if (p == NULL) return NULL; 26. /* now check that % is unique */ 27. p++; 28. q = strchr(p, '%'); 29. while (q != NULL && *(q + 1) == '%') /* skip %% */ 30. q = strchr(q + 2, '%'); 31. if (q != NULL) return NULL; /* % not unique */ 32. q = p + strspn(p, " -0+#"); /* skip past flags */ 33. q += strspn(q, "0123456789"); /* skip past field width */ 34. if (*q == '.') { q++; q += strspn(q, "0123456789"); } 35. /* skip past precision */ 36. if (strchr("eEfFgG", *q) == NULL) return NULL; 37. /* not a floating-point format */ 38. return p; 39. } 40. 41. JNIEXPORT void JNICALL Java_Printf3_fprint(JNIEnv* env, jclass cl, 42. jobject out, jstring format, jdouble x) 43. { 44. const char* cformat; 45. char* fmt; 46. jstring str; 47. jclass class_PrintWriter; 48. jmethodID id_print; 49. 50. cformat = (*env)->GetStringUTFChars(env, format, NULL); 51. fmt = find_format(cformat); 52. if (fmt == NULL) 53. str = format; 54. else 55. { 56. char* cstr; 57. int width = atoi(fmt); 58. if (width == 0) width = DBL_DIG + 10; 59. cstr = (char*) malloc(strlen(cformat) + width); 60. sprintf(cstr, cformat, x); 61. str = (*env)->NewStringUTF(env, cstr); 62. free(cstr); 63. } 64. (*env)->ReleaseStringUTFChars(env, format, cformat); 65. 66. /* now call ps.print(str) */ 67. 68. /* get the class */ 69. class_PrintWriter = (*env)->GetObjectClass(env, out); 70. 71. /* get the method ID */ 72. id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(Ljava/lang/String;)V"); 73. 74. /* call the method */ 75. (*env)->CallVoidMethod(env, out, id_print, str); 76. }
All array types of the Java programming language have corresponding C types, as shown in Table 12-2.
In C, all these array types are actually type synonyms of jobject
. In C++, however, they are arranged in the inheritance hierarchy shown in Figure 12-3. The type jarray
denotes a generic array.
The GetArrayLength
function returns the length of an array.
jarray array = . . .; jsize length = (*env)->GetArrayLength(env, array);
How you access elements in the array depends on whether the array stores objects or a primitive type (bool
, char
, or a numeric type). You access elements in an object array with the GetObjectArrayElement
and SetObjectArrayElement
methods.
jobjectArray array = . . .; int i, j; jobject x = (*env)->GetObjectArrayElement(env, array, i); (*env)->SetObjectArrayElement(env, array, j, x);
Although simple, this approach is also clearly inefficient; you want to be able to access array elements directly, especially when doing vector and matrix computations.
The Get
XxxArrayElements
function returns a C pointer to the starting element of the array. As with ordinary strings, you must remember to call the corresponding Release
XxxArrayElements
function to tell the virtual machine when you no longer need that pointer. Here, the type Xxx must be a primitive type; that is, not Object
. You can then read and write the array elements directly. However, because the pointer might point to a copy, any changes that you make are guaranteed to be reflected in the original array only when you call the corresponding Release
XxxArrayElements
function!
You can find out if an array is a copy by passing a pointer to a jboolean
variable as the third parameter to a Get
XxxArrayElements
method. The variable is filled with JNI_TRUE
if the array is a copy. If you aren’t interested in that information, just pass a NULL
pointer.
Here is a code sample that multiplies all elements in an array of double
values by a constant. We obtain a C pointer a
into the Java array and then access individual elements as a[i]
.
jdoubleArray array_a = . . .; double scaleFactor = . . .; double* a = (*env)->GetDoubleArrayElements(env, array_a, NULL); for (i = 0; i < (*env)->GetArrayLength(env, array_a); i++) a[i] = a[i] * scaleFactor; (*env)->ReleaseDoubleArrayElements(env, array_a, a, 0);
Whether the virtual machine actually copies the array depends on how it allocates arrays and does its garbage collection. Some “copying” garbage collectors routinely move objects around and update object references. That strategy is not compatible with “pinning” an array to a particular location, because the collector cannot update the pointer values in native code.
In the Sun JVM implementation, boolean
arrays are represented as packed arrays of 32-bit words. The GetBooleanArrayElements
method copies them into unpacked arrays of jboolean
values.
To access just a few elements of a large array, use the Get
XxxArrayRegion
and Set
XxxArrayRegion
methods that copy a range of elements from the Java array into a C array and back.
You can create new Java arrays in native methods with the New
XxxArray
function. To create a new array of objects, you specify the length, the type of the array elements, and an initial element for all entries (typically, NULL
). Here is an example.
jclass class_Employee = (*env)->FindClass(env, "Employee"); jobjectArray array_e = (*env)->NewObjectArray(env, 100, class_Employee, NULL);
Arrays of primitive types are simpler. You just supply the length of the array.
jdoubleArray array_d = (*env)->NewDoubleArray(env, 100);
The array is then filled with zeroes.
Java SE 1.4 added three methods to the JNI API:
jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) void* GetDirectBufferAddress(JNIEnv* env, jobject buf) jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf)
Direct buffers are used in the java.nio
package to support more efficient input/output operations and to minimize the copying of data between native and Java arrays.
Native methods are a significant security risk to programs in the Java programming language. The C runtime system has no protection against array bounds errors, indirection through bad pointers, and so on. It is particularly important that programmers of native methods handle all error conditions to preserve the integrity of the Java platform. In particular, when your native method diagnoses a problem that it cannot handle, it should report this problem to the Java virtual machine. Then, you would naturally throw an exception in this situation. However, C has no exceptions. Instead, you must call the Throw
or ThrowNew
function to create a new exception object. When the native method exits, the Java virtual machine throws that exception.
To use the Throw
function, call NewObject
to create an object of a subtype of Throwable
. For example, here we allocate an EOFException
object and throw it.
jclass class_EOFException = (*env)->FindClass(env, "java/io/EOFException"); jmethodID id_EOFException = (*env)->GetMethodID(env, class_EOFException, "<init>", "()V"); /* ID of default constructor */ jthrowable obj_exc = (*env)->NewObject(env, class_EOFException, id_EOFException); (*env)->Throw(env, obj_exc);
It is usually more convenient to call ThrowNew
, which constructs an exception object, given a class and a “modified UTF-8” byte sequence.
(*env)->ThrowNew(env, (*env)->FindClass(env, "java/io/EOFException"), "Unexpected end of file");
Both Throw
and ThrowNew
merely post the exception; they do not interrupt the control flow of the native method. Only when the method returns does the Java virtual machine throw the exception. Therefore, every call to Throw
and ThrowNew
should always immediately be followed by a return
statement.
If you implement native methods in C++, you cannot throw a Java exception object in your C++ code. In a C++ binding, it would be possible to implement a translation between exceptions in the C++ and Java programming languages—however, this is not currently implemented. Use Throw
or ThrowNew
to throw a Java exception in a native C++ method, and make sure that your native methods throw no C++ exceptions.
Normally, native code need not be concerned with catching Java exceptions. However, when a native method calls a Java method, that method might throw an exception. Moreover, a number of the JNI functions throw exceptions as well. For example, SetObjectArrayElement
throws an ArrayIndexOutOfBoundsException
if the index is out of bounds, and an ArrayStoreException
if the class of the stored object is not a subclass of the element class of the array. In situations like these, a native method should call the ExceptionOccurred
method to determine whether an exception has been thrown. The call
jthrowable obj_exc = (*env)->ExceptionOccurred(env);
returns NULL
if no exception is pending, or it returns a reference to the current exception object. If you just want to check whether an exception has been thrown, without obtaining a reference to the exception object, use
jboolean occurred = (*env)->ExceptionCheck(env);
Normally, a native method should simply return when an exception has occurred so that the virtual machine can propagate it to the Java code. However, a native method may analyze the exception object to determine if it can handle the exception. If it can, then the function
(*env)->ExceptionClear(env);
must be called to turn off the exception.
In our next example, we implement the fprint
native method with the paranoia that is appropriate for a native method. Here are the exceptions that we throw:
A NullPointerException
if the format string is NULL
.
An IllegalArgumentException
if the format string doesn’t contain a %
specifier that is appropriate for printing a double
.
An OutOfMemoryError
if the call to malloc
fails.
Finally, to demonstrate how to check for an exception when calling a Java method from a native method, we send the string to the stream, a character at a time, and call ExceptionOccurred
after each call. Listing 12-17 shows the code for the native method, and Listing 12-18 contains the definition of the class containing the native method. Notice that the native method does not immediately terminate when an exception occurs in the call to PrintWriter.print
—it first frees the cstr
buffer. When the native method returns, the virtual machine again raises the exception. The test program in Listing 12-19 demonstrates how the native method throws an exception when the formatting string is not valid.
Example 12-17. Printf4.c
1. /** 2. @version 1.10 1997-07-01 3. @author Cay Horstmann 4. */ 5. 6. #include "Printf4.h" 7. #include <string.h> 8. #include <stdlib.h> 9. #include <float.h> 10. 11. /** 12. @param format a string containing a printf format specifier 13. (such as "%8.2f"). Substrings "%%" are skipped. 14. @return a pointer to the format specifier (skipping the '%') 15. or NULL if there wasn't a unique format specifier 16. */ 17. char* find_format(const char format[]) 18. { 19. char* p; 20. char* q; 21. 22. p = strchr(format, '%'); 23. while (p != NULL && *(p + 1) == '%') /* skip %% */ 24. p = strchr(p + 2, '%'); 25. if (p == NULL) return NULL; 26. /* now check that % is unique */ 27. p++; 28. q = strchr(p, '%'); 29. while (q != NULL && *(q + 1) == '%') /* skip %% */ 30. q = strchr(q + 2, '%'); 31. if (q != NULL) return NULL; /* % not unique */ 32. q = p + strspn(p, " -0+#"); /* skip past flags */ 33. q += strspn(q, "0123456789"); /* skip past field width */ 34. if (*q == '.') { q++; q += strspn(q, "0123456789"); } 35. /* skip past precision */ 36. if (strchr("eEfFgG", *q) == NULL) return NULL; 37. /* not a floating-point format */ 38. return p; 39. } 40. 41. JNIEXPORT void JNICALL Java_Printf4_fprint(JNIEnv* env, jclass cl, 42. jobject out, jstring format, jdouble x) 43. { 44. const char* cformat; 45. char* fmt; 46. jclass class_PrintWriter; 47. jmethodID id_print; 48. char* cstr; 49. int width; 50. int i; 51. 52. if (format == NULL) 53. { 54. (*env)->ThrowNew(env, 55. (*env)->FindClass(env, 56. "java/lang/NullPointerException"), 57. "Printf4.fprint: format is null"); 58. return; 59. } 60. 61. cformat = (*env)->GetStringUTFChars(env, format, NULL); 62. fmt = find_format(cformat); 63. 64. if (fmt == NULL) 65. { 66. (*env)->ThrowNew(env, 67. (*env)->FindClass(env, 68. "java/lang/IllegalArgumentException"), 69. "Printf4.fprint: format is invalid"); 70. return; 71. } 72. 73. width = atoi(fmt); 74. if (width == 0) width = DBL_DIG + 10; 75. cstr = (char*)malloc(strlen(cformat) + width); 76. 77. if (cstr == NULL) 78. { 79. (*env)->ThrowNew(env, 80. (*env)->FindClass(env, "java/lang/OutOfMemoryError"), 81. "Printf4.fprint: malloc failed"); 82. return; 83. } 84. 85. sprintf(cstr, cformat, x); 86. 87. (*env)->ReleaseStringUTFChars(env, format, cformat); 88. 89. /* now call ps.print(str) */ 90. 91. /* get the class */ 92. class_PrintWriter = (*env)->GetObjectClass(env, out); 93. 94. /* get the method ID */ 95. id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(C)V"); 96. 97. /* call the method */ 98. for (i = 0; cstr[i] != 0 && !(*env)->ExceptionOccurred(env); i++) 99. (*env)->CallVoidMethod(env, out, id_print, cstr[i]); 100. 101. free(cstr); 102. }
Example 12-19. Printf4Test.java
1. import java.io.*; 2. 3. /** 4. * @version 1.10 1997-07-01 5. * @author Cay Horstmann 6. */ 7. class Printf4Test 8. { 9. public static void main(String[] args) 10. { 11. double price = 44.95; 12. double tax = 7.75; 13. double amountDue = price * (1 + tax / 100); 14. PrintWriter out = new PrintWriter(System.out); 15. /* This call will throw an exception--note the %% */ 16. Printf4.fprint(out, "Amount due = %%8.2f ", amountDue); 17. out.flush(); 18. } 19. }
Up to now, we have considered programs in the Java programming language that made a few C calls, presumably because C was faster or allowed access to functionality that was inaccessible from the Java platform. Suppose you are in the opposite situation. You have a C or C++ program and would like to make calls to Java code. The invocation API enables you to embed the Java virtual machine into a C or C++ program. Here is the minimal code that you need to initialize a virtual machine:
JavaVMOption options[1]; JavaVMInitArgs vm_args; JavaVM *jvm; JNIEnv *env; options[0].optionString = "-Djava.class.path=."; memset(&vm_args, 0, sizeof(vm_args)); vm_args.version = JNI_VERSION_1_2; vm_args.nOptions = 1; vm_args.options = options; JNI_CreateJavaVM(&jvm, (void**) &env, &vm_args);
The call to JNI_CreateJavaVM
creates the virtual machine and fills in a pointer, jvm
, to the virtual machine and a pointer, env
, to the execution environment.
You can supply any number of options to the virtual machine. Simply increase the size of the options
array and the value of vm_args.nOptions
. For example,
options[i].optionString = "-Djava.compiler=NONE";
deactivates the just-in-time compiler.
When you run into trouble and your program crashes, refuses to initialize the JVM, or can’t load your classes, then turn on the JNI debugging mode. Set an option to
options[i].optionString = "-verbose:jni";
You will see a flurry of messages that indicate the progress in initializing the JVM. If you don’t see your classes loaded, check both your path and your class path settings.
Once you have set up the virtual machine, you can call Java methods in the way described in the preceding sections: Simply use the env
pointer in the usual way.
You need the jvm
pointer only to call other functions in the invocation API. Currently, there are only four such functions. The most important one is the function to terminate the virtual machine:
(*jvm)->DestroyJavaVM(jvm);
Unfortunately, under Windows, it has become difficult to dynamically link to the JNI_CreateJavaVM
function in the jre/bin/client/jvm.dll
library, due to changed linking rules in Vista and Sun’s reliance on an older C runtime library. Our sample program overcomes this problem by loading the library manually. This is the same approach used by the java
program—see the file launcher/java_md.c
in the src.jar
file that is a part of the JDK.
The C program in Listing 12-20 sets up a virtual machine and then calls the main
method of the Welcome
class, which was discussed in Volume I, Chapter 2. (Make sure to compile the Welcome.java
file before starting the invocation test program.)
Example 12-20. InvocationTest.c
1. /** 2. @version 1.20 2007-10-26 3. @author Cay Horstmann 4. */ 5. 6. #include <jni.h> 7. #include <stdlib.h> 8. 9. #ifdef _WINDOWS 10. 11. #include <windows.h> 12. static HINSTANCE loadJVMLibrary(void); 13. typedef jint (JNICALL *CreateJavaVM_t)(JavaVM **, void **, JavaVMInitArgs *); 14. 15. #endif 16. 17. int main() 18. { 19. JavaVMOption options[2]; 20. JavaVMInitArgs vm_args; 21. JavaVM *jvm; 22. JNIEnv *env; 23. long status; 24. 25. jclass class_Welcome; 26. jclass class_String; 27. jobjectArray args; 28. jmethodID id_main; 29. 30. #ifdef _WINDOWS 31. HINSTANCE hjvmlib; 32. CreateJavaVM_t createJavaVM; 33. #endif 34. 35. options[0].optionString = "-Djava.class.path=."; 36. 37. memset(&vm_args, 0, sizeof(vm_args)); 38. vm_args.version = JNI_VERSION_1_2; 39. vm_args.nOptions = 1; 40. vm_args.options = options; 41. 42. 43. #ifdef _WINDOWS 44. hjvmlib = loadJVMLibrary(); 45. createJavaVM = (CreateJavaVM_t) GetProcAddress(hjvmlib, "JNI_CreateJavaVM"); 46. status = (*createJavaVM)(&jvm, (void **) &env, &vm_args); 47. #else 48. status = JNI_CreateJavaVM(&jvm, (void **) &env, &vm_args); 49. #endif 50. 51. if (status == JNI_ERR) 52. { 53. fprintf(stderr, "Error creating VM "); 54. return 1; 55. } 56. 57. class_Welcome = (*env)->FindClass(env, "Welcome"); 58. id_main = (*env)->GetStaticMethodID(env, class_Welcome, "main", "([Ljava/lang/String;)V"); 59. 60. class_String = (*env)->FindClass(env, "java/lang/String"); 61. args = (*env)->NewObjectArray(env, 0, class_String, NULL); 62. (*env)->CallStaticVoidMethod(env, class_Welcome, id_main, args); 63. 64. (*jvm)->DestroyJavaVM(jvm); 65. 66. return 0; 67. } 68. 69. #ifdef _WINDOWS 70. 71. static int GetStringFromRegistry(HKEY key, const char *name, char *buf, jint bufsize) 72. { 73. DWORD type, size; 74. 75. return RegQueryValueEx(key, name, 0, &type, 0, &size) == 0 76. && type == REG_SZ 77. && size < (unsigned int) bufsize 78. && RegQueryValueEx(key, name, 0, 0, buf, &size) == 0; 79. } 80. 81. static void GetPublicJREHome(char *buf, jint bufsize) 82. { 83. HKEY key, subkey; 84. char version[MAX_PATH]; 85. 86. /* Find the current version of the JRE */ 87. char *JRE_KEY = "Software\JavaSoft\Java Runtime Environment"; 88. if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, JRE_KEY, 0, KEY_READ, &key) != 0) 89. { 90. fprintf(stderr, "Error opening registry key '%s' ", JRE_KEY); 91. exit(1); 92. } 93. 94. if (!GetStringFromRegistry(key, "CurrentVersion", version, sizeof(version))) 95. { 96. fprintf(stderr, "Failed reading value of registry key: %s\CurrentVersion ", JRE_KEY); 97. RegCloseKey(key); 98. exit(1); 99. } 100. 101. /* Find directory where the current version is installed. */ 102. if (RegOpenKeyEx(key, version, 0, KEY_READ, &subkey) != 0) 103. { 104. fprintf(stderr, "Error opening registry key '%s\%s' ", JRE_KEY, version); 105. RegCloseKey(key); 106. exit(1); 107. } 108. 109. if (!GetStringFromRegistry(subkey, "JavaHome", buf, bufsize)) 110. { 111. fprintf(stderr, "Failed reading value of registry key: %s\%s\JavaHome ", 112. JRE_KEY, version); 113. RegCloseKey(key); 114. RegCloseKey(subkey); 115. exit(1); 116. } 117. 118. RegCloseKey(key); 119. RegCloseKey(subkey); 120. } 121. 122. static HINSTANCE loadJVMLibrary(void) 123. { 124. HINSTANCE h1, h2; 125. char msvcdll[MAX_PATH]; 126. char javadll[MAX_PATH]; 127. GetPublicJREHome(msvcdll, MAX_PATH); 128. strcpy(javadll, msvcdll); 129. strncat(msvcdll, "\bin\msvcr71.dll", MAX_PATH - strlen(msvcdll)); 130. msvcdll[MAX_PATH - 1] = ' '; 131. strncat(javadll, "\bin\client\jvm.dll", MAX_PATH - strlen(javadll)); 132. javadll[MAX_PATH - 1] = ' '; 133. 134. h1 = LoadLibrary(msvcdll); 135. if (h1 == NULL) 136. { 137. fprintf(stderr, "Can't load library msvcr71.dll "); 138. exit(1); 139. } 140. 141. h2 = LoadLibrary(javadll); 142. if (h2 == NULL) 143. { 144. fprintf(stderr, "Can't load library jvm.dll "); 145. exit(1); 146. } 147. return h2; 148. } 149. 150. #endif
To compile this program under Linux, use
gcc -I jdk/include -I jdk/include/linux -o InvocationTest -L jdk/jre/lib/i386/client -ljvm InvocationTest.c
Under Solaris, use
cc -I jdk/include -I jdk/include/solaris -o InvocationTest -L jdk/jre/lib/sparc -ljvm InvocationTest.c
When compiling in Windows with the Microsoft compiler, use the command line
cl -D_WINDOWS -I jdkinclude -I jdkincludewin32 InvocationTest.c jdklibjvm.lib advapi32.lib
You will need to make sure that the INCLUDE
and LIB
environment variables include the paths to the Windows API header and library files.
With Cygwin, you compile with
gcc -D_WINDOWS -mno-cygwin -I jdkinclude -I jdkincludewin32 -D__int64="long long" -I c:cygwinusrincludew32api -o InvocationTest
Before you run the program under Linux/UNIX, make sure that the LD_LIBRARY_PATH
contains the directories for the shared libraries. For example, if you use the bash
shell on Linux, issue the following command:
export LD_LIBRARY_PATH=jdk/jre/lib/i386/client:$LD_LIBRARY_PATH
In this section, we describe a full, working example that covers everything we discussed in this chapter: using native methods with strings, arrays, objects, constructor calls, and error handling. We show you how to put a Java platform wrapper around a subset of the ordinary C-based API used to work with the Windows registry. Of course, being a Windows-specific feature, a program using the Windows registry is inherently nonportable. For that reason, the standard Java library has no support for the registry, and it makes sense to use native methods to gain access to it.
The Windows registry is a data depository that holds configuration information for the Windows operating system and application programs. It provides a single point for administration and backup of system and application preferences. On the downside, the registry is also a single point of failure—if you mess up the registry, your computer could malfunction or even fail to boot!
We don’t suggest that you use the registry to store configuration parameters for your Java programs. The Java preferences API is a better solution—see Volume I, Chapter 10 for more information. We simply use the registry to demonstrate how to wrap a nontrivial native API into a Java class.
The principal tool for inspecting the registry is the registry editor. Because of the potential for error by naive but enthusiastic users, there is no icon for launching the registry editor. Instead, start a DOS shell (or open the Start -> Run dialog box) and type regedit
. Figure 12-4 shows the registry editor in action.
The left side shows the keys, which are arranged in a tree structure. Note that each key starts with one of the HKEY
nodes like
HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE . . .
The right side shows the name/value pairs that are associated with a particular key. For example, if you installed Java SE 6, the key
HKEY_LOCAL_MACHINESoftwareJavaSoftJava Runtime Environment
contains a name/value pair such as
CurrentVersion="1.6.0_03"
In this case, the value is a string. The values can also be integers or arrays of bytes.
We implement a simple interface to access the registry from Java code, and then implement this interface with native code. Our interface allows only a few registry operations; to keep the code size down, we omitted other important operations such as adding, deleting, and enumerating keys. (It would be easy to add the remaining registry API functions.)
Even with the limited subset that we supply, you can
Enumerate all names stored in a key.
Read the value stored with a name.
Set the value stored with a name.
Here is the Java class that encapsulates a registry key.
public class Win32RegKey { public Win32RegKey(int theRoot, String thePath) { . . . } public Enumeration names() { . . . } public native Object getValue(String name); public native void setValue(String name, Object value); public static final int HKEY_CLASSES_ROOT = 0×80000000; public static final int HKEY_CURRENT_USER = 0×80000001; public static final int HKEY_LOCAL_MACHINE = 0×80000002; . . . }
The names
method returns an enumeration that holds all the names stored with the key. You can get at them with the familiar hasMoreElements/nextElement
methods. The getValue
method returns an object that is either a string, an Integer
object, or a byte array. The value parameter of the setValue
method must also be of one of these three types.
We need to implement three actions:
Get the value of a key.
Set the value of a key.
Iterate through the names of a key.
Fortunately, you have seen essentially all the tools that are required, such as the conversion between Java strings and arrays and those of C. You also saw how to raise a Java exception in case something goes wrong.
Two issues make these native methods more complex than the preceding examples. The getValue
and setValue
methods deal with the type Object
, which can be one of String
, Integer
, or byte[]
. The enumeration object stores the state between successive calls to hasMoreElements
and nextElement
.
Let us first look at the getValue
method. The method (which is shown in Listing 12-22) goes through the following steps:
Opens the registry key. To read their values, the registry API requires that keys be open.
Queries the type and size of the value that is associated with the name.
Reads the data into a buffer.
Calls NewStringUTF
to create a new string with the value data if the type is REG_SZ
(a string).
Invokes the Integer
constructor if the type is REG_DWORD
(a 32-bit integer).
Calls NewByteArray
to create a new byte array, then SetByteArrayRegion
to copy the value data into the byte array, if the type is REG_BINARY
.
If the type is none of these or if an error occurred when an API function was called, throws an exception and releases all resources that had been acquired up to that point.
Closes the key and returns the object (String
, Integer
, or byte[]
) that had been created.
As you can see, this example illustrates quite nicely how to generate Java objects of different types.
In this native method, coping with the generic return type is not difficult. The jstring
, jobject
, or jarray
reference is simply returned as a jobject
. However, the setValue
method receives a reference to an Object
and must determine the Object
’s exact type to save the Object
as a string, integer, or byte array. We can make this determination by querying the class of the value
object, finding the class references for java.lang.String
, java.lang.Integer
, and byte[]
, and comparing them with the IsAssignableFrom
function.
If class1
and class2
are two class references, then the call
(*env)->IsAssignableFrom(env, class1, class2)
returns JNI_TRUE
when class1
and class2
are the same class or when class1
is a subclass of class2
. In either case, references to objects of class1
can be cast to class2
. For example, when
(*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, value), (*env)->FindClass(env, "[B"))
is true
, then we know that value
is a byte array.
Here is an overview of the steps in the setValue
method.
Opens the registry key for writing.
Finds the type of the value to write.
Calls GetStringUTFChars
to get a pointer to the characters if the type is String
.
Calls the intValue
method to get the integer stored in the wrapper object if the type is Integer
.
Calls GetByteArrayElements
to get a pointer to the bytes if the type is byte[]
.
Passes the data and length to the registry.
Closes the key
Releases the pointer to the data if the type is String
or byte[]
.
Finally, let us turn to the native methods that enumerate keys. These are methods of the Win32RegKeyNameEnumeration
class (see Listing 12-21). When the enumeration process starts, we must open the key. For the duration of the enumeration, we must retain the key handle. That is, the key handle must be stored with the enumeration object. The key handle is of type DWORD
, a 32-bit quantity, and, hence, can be stored in a Java integer. It is stored in the hkey
field of the enumeration class. When the enumeration starts, the field is initialized with SetIntField
. Subsequent calls read the value with GetIntField
.
In this example, we store three other data items with the enumeration object. When the enumeration first starts, we can query the registry for the count of name/value pairs and the length of the longest name, which we need so we can allocate C character arrays to hold the names. These values are stored in the count
and maxsize
fields of the enumeration object. Finally, the index
field is initialized with −1 to indicate the start of the enumeration, is set to 0
once the other instance fields are initialized, and is incremented after every enumeration step.
Let’s walk through the native methods that support the enumeration. The hasMoreElements
method is simple:
Retrieves the index
and count
fields.
If the index is −1, calls the startNameEnumeration
function, which opens the key, queries the count and maximum length, and initializes the hkey
, count
, maxsize
, and index
fields.
Returns JNI_TRUE
if index
is less than count
; JNI_FALSE
otherwise.
The nextElement
method needs to work a little harder:
Retrieves the index
and count
fields.
If the index is −1, calls the startNameEnumeration
function, which opens the key, queries the count and maximum length, and initializes the hkey
, count
, maxsize
, and index
fields.
If index
equals count
, throws a NoSuchElementException
.
Reads the next name from the registry.
Increments index
.
If index
equals count
, closes the key.
Before compiling, remember to run javah
on both Win32RegKey
and Win32RegKeyNameEnumeration
. The complete command line for the Microsoft compiler is
cl -I jdkinclude -I jdkincludewin32 -LD Win32RegKey.c advapi32.lib -FeWin32RegKey.dll
With Cygwin, use
gcc -mno-cygwin -D __int64="long long" -I jdkinclude -I jdkincludewin32 -I c:cygwinusrincludew32api -shared -Wl,--add-stdcall-alias -o Win32RegKey.dll Win32RegKey.c
Because the registry API is specific to Windows, this program will not work on other operating systems.
Listing 12-23 shows a program to test our new registry functions. We add three name/value pairs, a string, an integer, and a byte array to the key.
HKEY_CURRENT_USERSoftwareJavaSoftJava Runtime Environment
We then enumerate all names of that key and retrieve their values. The program will print
Default user=Harry Hacker Lucky number=13 Small primes=2 3 5 7 11 13
Although adding these name/value pairs to that key probably does no harm, you might want to use the registry editor to remove them after running this program.
Example 12-21. Win32RegKey.java
1. import java.util.*; 2. 3. /** 4. * A Win32RegKey object can be used to get and set values of a registry key in the Windows 5. * registry. 6. * @version 1.00 1997-07-01 7. * @author Cay Horstmann 8. */ 9. public class Win32RegKey 10. { 11. /** 12. * Construct a registry key object. 13. * @param theRoot one of HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, 14. * HKEY_USERS, HKEY_CURRENT_CONFIG, HKEY_DYN_DATA 15. * @param thePath the registry key path 16. */ 17. public Win32RegKey(int theRoot, String thePath) 18. { 19. root = theRoot; 20. path = thePath; 21. } 22. 23. /** 24. * Enumerates all names of registry entries under the path that this object describes. 25. * @return an enumeration listing all entry names 26. */ 27. public Enumeration<String> names() 28. { 29. return new Win32RegKeyNameEnumeration(root, path); 30. } 31. 32. /** 33. * Gets the value of a registry entry. 34. * @param name the entry name 35. * @return the associated value 36. */ 37. public native Object getValue(String name); 38. /** 39. * Sets the value of a registry entry. 40. * @param name the entry name 41. * @param value the new value 42. */ 43. 44. public native void setValue(String name, Object value); 45. 46. public static final int HKEY_CLASSES_ROOT = 0×80000000; 47. public static final int HKEY_CURRENT_USER = 0×80000001; 48. public static final int HKEY_LOCAL_MACHINE = 0×80000002; 49. public static final int HKEY_USERS = 0×80000003; 50. public static final int HKEY_CURRENT_CONFIG = 0×80000005; 51. public static final int HKEY_DYN_DATA = 0×80000006; 52. 53. private int root; 54. private String path; 55. 56. static 57. { 58. System.loadLibrary("Win32RegKey"); 59. } 60. } 61. 62. class Win32RegKeyNameEnumeration implements Enumeration<String> 63. { 64. Win32RegKeyNameEnumeration(int theRoot, String thePath) 65. { 66. root = theRoot; 67. path = thePath; 68. } 69. 70. public native String nextElement(); 71. 72. public native boolean hasMoreElements(); 73. 74. private int root; 75. private String path; 76. private int index = -1; 77. private int hkey = 0; 78. private int maxsize; 79. private int count; 80. } 81. 82. class Win32RegKeyException extends RuntimeException 83. { 84. public Win32RegKeyException() 85. { 86. } 87. 88. public Win32RegKeyException(String why) 89. { 90. super(why); 91. } 92. }
Example 12-22. Win32RegKey.c
1. /** 2. @version 1.00 1997-07-01 3. @author Cay Horstmann 4. */ 5. 6. #include "Win32RegKey.h" 7. #include "Win32RegKeyNameEnumeration.h" 8. #include <string.h> 9. #include <stdlib.h> 10. #include <windows.h> 11. 12. JNIEXPORT jobject JNICALL Java_Win32RegKey_getValue(JNIEnv* env, jobject this_obj, jobject name) 13. { 14. const char* cname; 15. jstring path; 16. const char* cpath; 17. HKEY hkey; 18. DWORD type; 19. DWORD size; 20. jclass this_class; 21. jfieldID id_root; 22. jfieldID id_path; 23. HKEY root; 24. jobject ret; 25. char* cret; 26. 27. /* get the class */ 28. this_class = (*env)->GetObjectClass(env, this_obj); 29. 30. /* get the field IDs */ 31. id_root = (*env)->GetFieldID(env, this_class, "root", "I"); 32. id_path = (*env)->GetFieldID(env, this_class, "path", "Ljava/lang/String;"); 33. 34. /* get the fields */ 35. root = (HKEY) (*env)->GetIntField(env, this_obj, id_root); 36. path = (jstring)(*env)->GetObjectField(env, this_obj, id_path); 37. cpath = (*env)->GetStringUTFChars(env, path, NULL); 38. 39. /* open the registry key */ 40. if (RegOpenKeyEx(root, cpath, 0, KEY_READ, &hkey) != ERROR_SUCCESS) 41. { 42. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 43. "Open key failed"); 44. (*env)->ReleaseStringUTFChars(env, path, cpath); 45. return NULL; 46. } 47. 48. (*env)->ReleaseStringUTFChars(env, path, cpath); 49. cname = (*env)->GetStringUTFChars(env, name, NULL); 50. 51. /* find the type and size of the value */ 52. if (RegQueryValueEx(hkey, cname, NULL, &type, NULL, &size) != ERROR_SUCCESS) 53. { 54. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 55. "Query value key failed"); 56. RegCloseKey(hkey); 57. (*env)->ReleaseStringUTFChars(env, name, cname); 58. return NULL; 59. } 60. 61. /* get memory to hold the value */ 62. cret = (char*)malloc(size); 63. 64. /* read the value */ 65. if (RegQueryValueEx(hkey, cname, NULL, &type, cret, &size) != ERROR_SUCCESS) 66. { 67. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 68. "Query value key failed"); 69. free(cret); 70. RegCloseKey(hkey); 71. (*env)->ReleaseStringUTFChars(env, name, cname); 72. return NULL; 73. } 74. 75. /* depending on the type, store the value in a string, 76. integer or byte array */ 77. if (type == REG_SZ) 78. { 79. ret = (*env)->NewStringUTF(env, cret); 80. } 81. else if (type == REG_DWORD) 82. { 83. jclass class_Integer = (*env)->FindClass(env, "java/lang/Integer"); 84. /* get the method ID of the constructor */ 85. jmethodID id_Integer = (*env)->GetMethodID(env, class_Integer, "<init>", "(I)V"); 86. int value = *(int*) cret; 87. /* invoke the constructor */ 88. ret = (*env)->NewObject(env, class_Integer, id_Integer, value); 89. } 90. else if (type == REG_BINARY) 91. { 92. ret = (*env)->NewByteArray(env, size); 93. (*env)->SetByteArrayRegion(env, (jarray) ret, 0, size, cret); 94. } 95. else 96. { 97. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 98. "Unsupported value type"); 99. ret = NULL; 100. } 101. 102. free(cret); 103. RegCloseKey(hkey); 104. (*env)->ReleaseStringUTFChars(env, name, cname); 105. 106. return ret; 107. } 108. 109. JNIEXPORT void JNICALL Java_Win32RegKey_setValue(JNIEnv* env, jobject this_obj, 110. jstring name, jobject value) 111. { 112. const char* cname; 113. jstring path; 114. const char* cpath; 115. HKEY hkey; 116. DWORD type; 117. DWORD size; 118. jclass this_class; 119. jclass class_value; 120. jclass class_Integer; 121. jfieldID id_root; 122. jfieldID id_path; 123. HKEY root; 124. const char* cvalue; 125. int ivalue; 126. 127. /* get the class */ 128. this_class = (*env)->GetObjectClass(env, this_obj); 129. 130. /* get the field IDs */ 131. id_root = (*env)->GetFieldID(env, this_class, "root", "I"); 132. id_path = (*env)->GetFieldID(env, this_class, "path", "Ljava/lang/String;"); 133. 134. /* get the fields */ 135. root = (HKEY)(*env)->GetIntField(env, this_obj, id_root); 136. path = (jstring)(*env)->GetObjectField(env, this_obj, id_path); 137. cpath = (*env)->GetStringUTFChars(env, path, NULL); 138. 139. /* open the registry key */ 140. if (RegOpenKeyEx(root, cpath, 0, KEY_WRITE, &hkey) != ERROR_SUCCESS) 141. { 142. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 143. "Open key failed"); 144. (*env)->ReleaseStringUTFChars(env, path, cpath); 145. return; 146. } 147. 148. (*env)->ReleaseStringUTFChars(env, path, cpath); 149. cname = (*env)->GetStringUTFChars(env, name, NULL); 150. 151. class_value = (*env)->GetObjectClass(env, value); 152. class_Integer = (*env)->FindClass(env, "java/lang/Integer"); 153. /* determine the type of the value object */ 154. if ((*env)->IsAssignableFrom(env, class_value, (*env)->FindClass(env, "java/lang/String"))) 155. { 156. /* it is a string--get a pointer to the characters */ 157. cvalue = (*env)->GetStringUTFChars(env, (jstring) value, NULL); 158. type = REG_SZ; 159. size = (*env)->GetStringLength(env, (jstring) value) + 1; 160. } 161. else if ((*env)->IsAssignableFrom(env, class_value, class_Integer)) 162. { 163. /* it is an integer--call intValue to get the value */ 164. jmethodID id_intValue = (*env)->GetMethodID(env, class_Integer, "intValue", "()I"); 165. ivalue = (*env)->CallIntMethod(env, value, id_intValue); 166. type = REG_DWORD; 167. cvalue = (char*)&ivalue; 168. size = 4; 169. } 170. else if ((*env)->IsAssignableFrom(env, class_value, (*env)->FindClass(env, "[B"))) 171. { 172. /* it is a byte array--get a pointer to the bytes */ 173. type = REG_BINARY; 174. cvalue = (char*)(*env)->GetByteArrayElements(env, (jarray) value, NULL); 175. size = (*env)->GetArrayLength(env, (jarray) value); 176. } 177. else 178. { 179. /* we don't know how to handle this type */ 180. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 181. "Unsupported value type"); 182. RegCloseKey(hkey); 183. (*env)->ReleaseStringUTFChars(env, name, cname); 184. return; 185. } 186. 187. /* set the value */ 188. if (RegSetValueEx(hkey, cname, 0, type, cvalue, size) != ERROR_SUCCESS) 189. { 190. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 191. "Set value failed"); 192. } 193. 194. RegCloseKey(hkey); 195. (*env)->ReleaseStringUTFChars(env, name, cname); 196. 197. /* if the value was a string or byte array, release the pointer */ 198. if (type == REG_SZ) 199. { 200. (*env)->ReleaseStringUTFChars(env, (jstring) value, cvalue); 201. } 202. else if (type == REG_BINARY) 203. { 204. (*env)->ReleaseByteArrayElements(env, (jarray) value, (jbyte*) cvalue, 0); 205. } 206. } 207. 208. /* helper function to start enumeration of names */ 209. static int startNameEnumeration(JNIEnv* env, jobject this_obj, jclass this_class) 210. { 211. jfieldID id_index; 212. jfieldID id_count; 213. jfieldID id_root; 214. jfieldID id_path; 215. jfieldID id_hkey; 216. jfieldID id_maxsize; 217. 218. HKEY root; 219. jstring path; 220. const char* cpath; 221. HKEY hkey; 222. DWORD maxsize = 0; 223. DWORD count = 0; 224. 225. /* get the field IDs */ 226. id_root = (*env)->GetFieldID(env, this_class, "root", "I"); 227. id_path = (*env)->GetFieldID(env, this_class, "path", "Ljava/lang/String;"); 228. id_hkey = (*env)->GetFieldID(env, this_class, "hkey", "I"); 229. id_maxsize = (*env)->GetFieldID(env, this_class, "maxsize", "I"); 230. id_index = (*env)->GetFieldID(env, this_class, "index", "I"); 231. id_count = (*env)->GetFieldID(env, this_class, "count", "I"); 232. 233. /* get the field values */ 234. root = (HKEY)(*env)->GetIntField(env, this_obj, id_root); 235. path = (jstring)(*env)->GetObjectField(env, this_obj, id_path); 236. cpath = (*env)->GetStringUTFChars(env, path, NULL); 237. 238. /* open the registry key */ 239. if (RegOpenKeyEx(root, cpath, 0, KEY_READ, &hkey) != ERROR_SUCCESS) 240. { 241. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 242. "Open key failed"); 243. (*env)->ReleaseStringUTFChars(env, path, cpath); 244. return -1; 245. } 246. (*env)->ReleaseStringUTFChars(env, path, cpath); 247. 248. /* query count and max length of names */ 249. if (RegQueryInfoKey(hkey, NULL, NULL, NULL, NULL, NULL, NULL, &count, &maxsize, 250. NULL, NULL, NULL) != ERROR_SUCCESS) 251. { 252. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 253. "Query info key failed"); 254. RegCloseKey(hkey); 255. return -1; 256. } 257. 258. /* set the field values */ 259. (*env)->SetIntField(env, this_obj, id_hkey, (DWORD) hkey); 260. (*env)->SetIntField(env, this_obj, id_maxsize, maxsize + 1); 261. (*env)->SetIntField(env, this_obj, id_index, 0); 262. (*env)->SetIntField(env, this_obj, id_count, count); 263. return count; 264. } 265. 266. JNIEXPORT jboolean JNICALL Java_Win32RegKeyNameEnumeration_hasMoreElements(JNIEnv* env, 267. jobject this_obj) 268. { jclass this_class; 269. jfieldID id_index; 270. jfieldID id_count; 271. int index; 272. int count; 273. /* get the class */ 274. this_class = (*env)->GetObjectClass(env, this_obj); 275. 276. /* get the field IDs */ 277. id_index = (*env)->GetFieldID(env, this_class, "index", "I"); 278. id_count = (*env)->GetFieldID(env, this_class, "count", "I"); 279. 280. index = (*env)->GetIntField(env, this_obj, id_index); 281. if (index == -1) /* first time */ 282. { 283. count = startNameEnumeration(env, this_obj, this_class); 284. index = 0; 285. } 286. else 287. count = (*env)->GetIntField(env, this_obj, id_count); 288. return index < count; 289. } 290. 291. JNIEXPORT jobject JNICALL Java_Win32RegKeyNameEnumeration_nextElement(JNIEnv* env, 292. jobject this_obj) 293. { 294. jclass this_class; 295. jfieldID id_index; 296. jfieldID id_hkey; 297. jfieldID id_count; 298. jfieldID id_maxsize; 299. 300. HKEY hkey; 301. int index; 302. int count; 303. DWORD maxsize; 304. 305. char* cret; 306. jstring ret; 307. 308. /* get the class */ 309. this_class = (*env)->GetObjectClass(env, this_obj); 310. 311. /* get the field IDs */ 312. id_index = (*env)->GetFieldID(env, this_class, "index", "I"); 313. id_count = (*env)->GetFieldID(env, this_class, "count", "I"); 314. id_hkey = (*env)->GetFieldID(env, this_class, "hkey", "I"); 315. id_maxsize = (*env)->GetFieldID(env, this_class, "maxsize", "I"); 316. 317. index = (*env)->GetIntField(env, this_obj, id_index); 318. if (index == -1) /* first time */ 319. { 320. count = startNameEnumeration(env, this_obj, this_class); 321. index = 0; 322. } 323. else 324. count = (*env)->GetIntField(env, this_obj, id_count); 325. 326. if (index >= count) /* already at end */ 327. { 328. (*env)->ThrowNew(env, (*env)->FindClass(env, "java/util/NoSuchElementException"), 329. "past end of enumeration"); 330. return NULL; 331. } 332. 333. maxsize = (*env)->GetIntField(env, this_obj, id_maxsize); 334. hkey = (HKEY)(*env)->GetIntField(env, this_obj, id_hkey); 335. cret = (char*)malloc(maxsize); 336. 337. /* find the next name */ 338. if (RegEnumValue(hkey, index, cret, &maxsize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) 339. { 340. (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), 341. "Enum value failed"); 342. free(cret); 343. RegCloseKey(hkey); 344. (*env)->SetIntField(env, this_obj, id_index, count); 345. return NULL; 346. } 347. 348. ret = (*env)->NewStringUTF(env, cret); 349. free(cret); 350. 351. /* increment index */ 352. index++; 353. (*env)->SetIntField(env, this_obj, id_index, index); 354. 355. if (index == count) /* at end */ 356. { 357. RegCloseKey(hkey); 358. } 359. 360. return ret; 361. }
Example 12-23. Win32RegKeyTest.java
1. import java.util.*; 2. 3. /** 4. @version 1.02 2007-10-26 5. @author Cay Horstmann 6. */ 7. public class Win32RegKeyTest 8. { 9. public static void main(String[] args) 10. { 11. Win32RegKey key = new Win32RegKey( 12. Win32RegKey.HKEY_CURRENT_USER, "Software\JavaSoft\Java Runtime Environment"); 13. 14. key.setValue("Default user", "Harry Hacker"); 15. key.setValue("Lucky number", new Integer(13)); 16. key.setValue("Small primes", new byte[] { 2, 3, 5, 7, 11 }); 17. 18. Enumeration<String> e = key.names(); 19. 20. while (e.hasMoreElements()) 21. { 22. String name = e.nextElement(); 23. System.out.print(name + "="); 24. 25. Object value = key.getValue(name); 26. 27. if (value instanceof byte[]) 28. for (byte b : (byte[]) value) System.out.print((b & 0xFF) + " "); 39. else 30. System.out.print(value); 31. 32. System.out.println(); 33. } 34. } 35. }
You have now reached the end of the second volume of Core Java, completing a long journey in which you encountered many advanced APIs. We started out with topics that every Java programmer needs to know: streams, XML, networking, datatabases, and internationalization. Three long chapters covered graphics and GUI programming. We concluded with very technical chapters on security, remote methods, annotation processing, and native methods. We hope that you enjoyed your tour through the the vast breadth of the Java APIs, and that you can apply your newly gained knowledge in your projects.
18.226.104.27