Chapter 12. Native Methods

<feature><title></title> <objective>

CALLING A C FUNCTION FROM A JAVA PROGRAM

</objective>
<objective>

NUMERIC PARAMETERS AND RETURN VALUES

</objective>
<objective>

STRING PARAMETERS

</objective>
<objective>

ACCESSING FIELDS

</objective>
<objective>

ENCODING SIGNATURES

</objective>
<objective>

CALLING JAVA METHODS

</objective>
<objective>

ACCESSING ARRAY ELEMENTS

</objective>
<objective>

HANDLING ERRORS

</objective>
<objective>

USING THE INVOCATION API

</objective>
<objective>

A COMPLETE EXAMPLE: ACCESSING THE WINDOWS REGISTRY

</objective>
</feature>

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.

C++ Note

C++ Note

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.

Calling a C Function from a Java Program

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.

Example 12-1. HelloNative.java

1. /**
2.  * @version 1.11 2007-10-26
3.  * @author Cay Horstmann
4.  */
5. class HelloNative
6. {
7.    public static native void greeting();
8. }

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:

  1. 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.

  2. Replace every period with an underscore, and append the prefix Java_. For example, Java_HelloNative_greeting or Java_com_horstmann_HelloNative_greeting.

  3. 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 _0xxxx, where xxxx is the sequence of four hexadecimal digits of the character’s Unicode value.

Note

Note

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.

Example 12-3. HelloNative.c

 1. /*
 2.    @version 1.10 1997-07-01
 3.    @author Cay Horstmann
 4. */
 5.
 6. #include "HelloNative.h"
 7. #include <stdio.h>
 8.
 9. JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv* env, jclass cl)
10. {
11.    printf("Hello Native World!
");
12. }

In this simple function, ignore the env and cl arguments. You’ll see their use later.

C++ Note

C++ Note

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.

Tip

Tip

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.

Note

Note

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.

Processing native code

Figure 12-1. Processing native code

Example 12-4. HelloNativeTest.java

 1. /**
 2.  * @version 1.11 2007-10-26
 3.  * @author Cay Horstmann
 4.  */
 5. class HelloNativeTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       HelloNative.greeting();
10.    }
11.
12.    static
13.    {
14.       System.loadLibrary("HelloNative");
15.    }
16. }

If you compile and run this program, the message “Hello, Native World!” is displayed in a terminal window.

Note

Note

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:

  1. Declare a native method in a Java class.

  2. Run javah to get a header file with a C declaration for the method.

  3. Implement the native method in C.

  4. Place the code in a shared library.

  5. Load that library in your Java program.

Note

Note

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.

Numeric Parameters and Return Values

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.

Table 12-1. Java Types and C Types

Java Programming Language

C Programming Language

Bytes

boolean

jboolean

1

byte

jbyte

1

char

jchar

2

short

jshort

2

int

jint

4

long

jlong

8

float

jfloat

4

double

jdouble

8

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.

Using printf for Formatting Numbers

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.

Example 12-5. Printf1.java

 1. /**
 2.  * @version 1.10 1997-07-01
 3.  * @author Cay Horstmann
 4.  */
 5. class Printf1
 6. {
 7.    public static native int print(int width, int precision, double x);
 8.
 9.    static
10.    {
11.       System.loadLibrary("Printf1");
12.    }
13. }

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.pf" 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. }

String Parameters

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.)

Note

Note

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;
}

Note

Note

Unless explicitly mentioned otherwise, all code in this chapter is C code.

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 env pointer

Figure 12-2. The env pointer

C++ Note

C++ Note

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.

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.

Example 12-9. Printf2.java

 1. /**
 2.  * @version 1.10 1997-07-01
 3.  * @author Cay Horstmann
 4.  */
 5. class Printf2
 6. {
 7.    public static native String sprint(String format, double x);
 8.
 9.    static
10.    {
11.       System.loadLibrary("Printf2");
12.    }
13. }

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.

Accessing Fields

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.

Accessing Instance Fields

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);
}

Caution

Caution

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

Accessing static fields is similar to accessing nonstatic fields. You use the GetStaticFieldID and GetStaticXxxField/SetStaticXxxField 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);

Encoding Signatures

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:

B

byte

C

char

D

double

F

float

I

int

J

long

Lclassname;

a class type

S

short

V

void

Z

boolean

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.

Tip

Tip

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
}

Note

Note

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.

Calling Java Methods

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.

Instance 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

  1. Obtaining the class of the implicit parameter.

  2. Obtaining the method ID.

  3. Making the call.

    /* 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.

Note

Note

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);

Static Methods

Calling static methods from native methods is similar to calling instance methods. There are two differences.

  • You use the GetStaticMethodID and CallStaticXxxMethod 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;

C++ Note

C++ Note

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.

Constructors

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.

Alternative Method Invocations

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 CallNonvirtualXxxMethod 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-15. Printf3.java

 1. import java.io.*;
 2.
 3. /**
 4.  * @version 1.10 1997-07-01
 5.  * @author Cay Horstmann
 6.  */
 7. class Printf3
 8. {
 9.    public static native void fprint(PrintWriter out, String format, double x);
10.
11.    static
12.    {
13.       System.loadLibrary("Printf3");
14.    }
15. }

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. }

 

Accessing Array Elements

All array types of the Java programming language have corresponding C types, as shown in Table 12-2.

Table 12-2. Correspondence Between Java Array Types and C Types

Java Type

C Type

boolean[]

jbooleanArray

byte[]

jbyteArray

char[]

jcharArray

int[]

jintArray

short[]

jshortArray

long[]

jlongArray

float[]

jfloatArray

double[]

jdoubleArray

Object[]

jobjectArray

C++ Note

C++ Note

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.

Inheritance hierarchy of array types

Figure 12-3. Inheritance hierarchy of array types

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 GetXxxArrayElements function returns a C pointer to the starting element of the array. As with ordinary strings, you must remember to call the corresponding ReleaseXxxArrayElements 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 ReleaseXxxArrayElements function!

Note

Note

You can find out if an array is a copy by passing a pointer to a jboolean variable as the third parameter to a GetXxxArrayElements 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.

Note

Note

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 GetXxxArrayRegion and SetXxxArrayRegion 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 NewXxxArray 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.

Note

Note

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.

Handling Errors

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.

C++ Note

C++ Note

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-18. Printf4.java

 1. import java.io.*;
 2.
 3. /**
 4.  * @version 1.10 1997-07-01
 5.  * @author Cay Horstmann
 6.  */
 7. class Printf4
 8. {
 9.    public static native void fprint(PrintWriter ps, String format, double x);
10.
11.    static
12.    {
13.       System.loadLibrary("Printf4");
14.    }
15. }

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. }

Using the Invocation API

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.

Tip

Tip

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

A Complete Example: Accessing the Windows Registry

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.

Overview of the Windows Registry

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 registry editor

Figure 12-4. The registry editor

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.

A Java Platform Interface for Accessing the Registry

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.

Implementation of Registry Access Functions as Native Methods

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:

  1. Opens the registry key. To read their values, the registry API requires that keys be open.

  2. Queries the type and size of the value that is associated with the name.

  3. Reads the data into a buffer.

  4. Calls NewStringUTF to create a new string with the value data if the type is REG_SZ (a string).

  5. Invokes the Integer constructor if the type is REG_DWORD (a 32-bit integer).

  6. 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.

  7. 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.

  8. 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.

  1. Opens the registry key for writing.

  2. Finds the type of the value to write.

  3. Calls GetStringUTFChars to get a pointer to the characters if the type is String.

  4. Calls the intValue method to get the integer stored in the wrapper object if the type is Integer.

  5. Calls GetByteArrayElements to get a pointer to the bytes if the type is byte[].

  6. Passes the data and length to the registry.

  7. Closes the key

  8. 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:

  1. Retrieves the index and count fields.

  2. 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.

  3. Returns JNI_TRUE if index is less than count; JNI_FALSE otherwise.

The nextElement method needs to work a little harder:

  1. Retrieves the index and count fields.

  2. 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.

  3. If index equals count, throws a NoSuchElementException.

  4. Reads the next name from the registry.

  5. Increments index.

  6. 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.104.27