We hope that you are convinced that Java code has a number of advantages over code written in languages like C or C++—even for platform-specific applications. Here, of course, it is not portability that is the issue but rather features like these:
You are more likely to produce bug-free code using Java than C or C++.
Multithreading is probably easier to code in Java than in any other language.
Networking code is a breeze.
Portability is simply a bonus that you may or may not want to take advantage of down the line.
While a “100% Pure Java” solution is nice in principle, realistically, for an application, there are situations where you will want to write (or use) code written in another language. (Such code is usually called native code.) There are three obvious reasons why this may be the right choice:
You have substantial amounts of tested and debugged code available in that language. Porting the code to Java would be time consuming, and the resulting code would need to be tested and debugged again.
Your application requires access to system features or devices, and using Java would be cumbersome, at best, or impossible, at worst.
Maximizing the speed of the code is essential. For example, the task may be time critical or it may be code that is used so often that optimizing it has a big payoff. (This is actually the least plausible reason. With just-in-time compilation (JIT), intensive computations coded in Java are not that much slower than compiled C code.)
If you are in one of these three situations, it might make sense to call the code from Java. Of course, with the usual security manager in place, once you start using native code, you are restricted to applications rather than applets. In particular, the native code library you are calling must exist on the client machine, and it must work with the client machine architecture.
To make calling native methods possible, Java comes with hooks for working with system libraries, and the JDK has a few tools to relieve some (but not all) of the tedium necessary.
The language you use for your native code doesn’t have to be C or C++; you could use code compiled with a FORTRAN compiler, if you have access to a Java-to-FORTRAN binding, or Visual Basic, if you have access to a Java binding for its types.
Still, keep in mind:
Use native methods and you lose portability.
Even when you distribute your program as an application, you must supply a separate native method library for every platform you wish to support. This means you must also educate your users on how to install these libraries! Also, while users may trust that applets can neither damage data nor steal confidential information, they may not want to extend the same trust to Java code that uses native method libraries. For that reason, many potential users will be reluctant to use Java programs that require native code. Aside from the security issue, native libraries are unlikely to be as safe as Java code, especially if they are written in a language like C or C++ that offers no protection against over-writing memory through invalid pointer usage. It is easy to write native methods that corrupt the Java virtual machine, compromise its security, or trash the operating system.
Thus, we suggest using native code only as a last resort. If you must gain access to a device such as the serial port in a Java program, then you may need to write native code. If you need to access an existing body of code, why not consider native methods as a stopgap measure and eventually port the code to Java? If you are concerned about efficiency, benchmark a Java implementation. In most cases, the speed of Java using a JIT will be sufficient. (Just-in-time compilers and other emerging compiler technologies will soon exist on all platforms that Java has been ported to. They already exist under Windows and Solaris.) A talk at the 1996 JavaOne conference showed this clearly. The implementers of the cryptography library at Sun Microsystems reported that a pure Java implementation of their cryptographic functions was more than adequate. It was true that they were not as fast as a C implementation would have been, but it turned out not to matter. The Java implementation was far faster than the network I/O. And this turns out to be the real bottleneck.
In summary, there is no point in sacrificing portability for a meaningless speed improvement; don’t go native until you determine that you have no other choice.
In this chapter, we describe the so-called Java Native Interface (JNI) binding. An earlier language binding (sometimes called the raw native interface) was used with Java 1.0, and a variation of that earlier binding is still being used by the Microsoft Virtual Machine. JavaSoft has assured developers that the JNI binding described here is a permanent part of Java, and that it needs to be supported by all Java Virtual Machines.
Finally, we use C as our language for native methods in this chapter because C is probably the language most often used for Java native methods. In particular, you’ll see how to make the correspondence between Java and C data types, feature names, and function calls. (This correspondence is usually called the C binding.)
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 direct correspondence between classes in Java and C++. Even when programming with C++, you must access the fields and methods of Java classes through C function calls.
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’ll assume it is the useful and venerable printf
function. You want to be able to call printf
from your Java programs. Java sensibly uses the keyword native
for a native method, and you will obviously need to encapsulate the printf
function in a class. So, you might write something like:
public class Printf { public native String printf(String s); }
You actually can compile this class but when you go to use it in another program, and call the Java interpreter, then the interpreter will tell you it doesn’t know how to find printf
—reporting an UnsatisfiedLinkError
. So the trick is to give the run time enough information so that it can link in this class. As you will soon see, under the JDK this requires a three-step process:
Generate a C stub for a function that translates between the Java call and the actual C function. The stub does this translation by taking information off the Java stack and passing it to the compiled C function.
Create a special shared library and export the stub from it.
Use a special Java method, called System.LoadLibrary
. to tell the Java run time to load the library from Step 2.
We now show you how to carry out these steps for various kinds of examples, starting from a trivial special-case use of printf
and ending with a realistic example involving the registry function for Windows—examples that are obviously not available directly from Java.
Let’s start with just about the simplest possible situation using printf
: calling a native method that prints the message “Hello, Native World.” Obviously we are not even tapping into the useful formatting features of printf
! Still, this is a good way for you to test that your C compiler works as expected before you try implementing more ambitious native methods.
As we mentioned earlier, you first need to declare the native method in a class. The native
keyword alerts the Java compiler that the method will be defined externally. Of course, native methods will contain no Java code, and the method header is followed immediately by a terminating semicolon. This means, as you saw in the example above, native method declarations look similar to abstract method declarations.
class HelloNative { public native static void greeting(); . . . }
In this particular example, note that we also declare the native method as static
. Native methods can be both static and nonstatic. Note that this method takes no parameters; we do not yet want to deal with parameter passing, not even implicit parameters.
Next, write a corresponding C function. You must name that function exactly the way the Java run-time system 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 corejava.Day.greeting
.
, Replace every period with an underscore, and prepend the prefix Java_
. For example, Java_ HelloNative_greeting
or Java_corejava_Day_greeting
.
If the class name contains characters that are not ASCII letters or digits—that is, '_'
, '$'
, or Unicode characters with code > '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 should run the javah
utility, which automatically generates the function names. To use javah
first, compile the Java file (given in Example 10-3):
javac HelloNative.java
Next, call the javah
utility to produce a C header file. The javah
executable can be found in the jdkin
directory.
javah -jni HelloNative
Using javah
creates a header file, HelloNative.h
, as in Example 10-1:
Example 10-1. HelloNative.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloNative */ #ifndef _Included_HelloNative #define _Included_HelloNative #ifdef __cplusplus extern "C" { #endif /* * Class: HelloNative * Method: greeting * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloNative_greeting (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif
As you can see, this file contains the declaration of a function Java_HelloNative_greeting
. (The strings 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.)
Be sure to use the -jni
flag when running javah
. Without that flag, you get the header file for the Java 1.0 binding.
Now, you simply have to copy the function prototype from the header file into the source file and to give the implementation code for the function, as shown in Example 10-2.
Example 10-2. HelloNative.c
#include "HelloNative.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloNative_greeting (JNIEnv* env, jclass cl) { printf("Hello, Native World! "); }
In this simple function, we 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 are called from Java as extern "C"
. For example,
#include "HelloNative.h"
#include <stdio.h>
extern "C"
JNIEXPORT void JNICALL Java_HelloNative_greeting
(JNIEnv* env, jclass cl)
{ printf("Hello, Native World!
");
}
Next, compile the C code into a dynamically loaded library. For example, with the Microsoft C++ compiler under Windows, the command you use is
cl -Ic:jdkinclude -Ic:jdkincludewin32 -LD HelloNative.c -FeHelloNative.dll
With the Sun compiler under Solaris, the command to use is
cc -G -I/usr/local/java/include - I/usr/local/java/include/solaris HelloNative.c -o libHelloNative.so
(You may need to use different paths to specify the locations of the header files, depending on which directory contains the JDK.)
If you use the Microsoft C++ compiler to compile DLLs from a DOS shell, first run the batch file
c:devstudiovcinvcvars32.bat
That batch file properly configures the command-line compiler by setting up the path and the environment variables needed by the compiler.
Finally, we need to add the call to the System.loadLibrary
method that ensures that Java will load the library prior to the first use of the class. The easiest way to do this is to use a static initialization block in the class that contains the native method, as in Example 10-3:
Example 10-3. HelloNative.java
class HelloNative
{ public native static void greeting();
static
{System.loadLibrary("HelloNative");
}
}
Assuming you have followed all the steps given above, you are now ready to run the HelloNativeTest
application shown in Example 10-4.
Example 10-4. HelloNativeTest.java
class HelloNativeTest { public static void main(String[] args) { HelloNative.greeting(); } }
If you compile and run this Java program, the message “Hello, Native World!” is displayed in a terminal window.
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 code, you will see that we have taken the first steps toward bridging the gap between Java and C!
When passing numbers between C and Java, you need to understand which C types correspond to which Java types. For example, while 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 Java, of course, an int
is always a 32-bit integer. For that reason, the Java Native Interface defines types jint
, jlong
, and so on.
Table 10-1 shows the correspondence between Java and C types.
Table 10-1. Java and C types
Java |
C |
bytes |
---|---|---|
|
|
1 |
|
|
1 |
|
|
2 |
|
|
2 |
|
|
4 |
|
|
8 |
|
|
4 |
|
|
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.
Recall that Java has no elegant way for formatted printing of floating-point numbers. (Of course, you can use the DecimalFormat
class (see Volume 1, Chapter 3) and build custom formats—we just don’t think this is as easy as a simple call to printf
.) Since printf
is quick, easy, and well known, let’s suppose you decide to implement the same functionality via a call to the printf
function in a native method.
We don’t actually recommend this approach the native code needs to be compiled for every target platform. We are using it because it shows off the techniques of passing parameters to a native method and obtaining a return value.
Example 10-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 10-5. Printf1.java
class Printf1 { public static native int print(int width, int precision, double x); static { System.loadLibrary("Printf1"); } }
Notice that when implementing the method in C, we must change all int
and double
parameters to jint
and jdouble
, as shown in Example 10-6.
Example 10-6. Printf1.c
#include "Printf1.h" #include <stdio.h> JNIEXPORT jint JNICALL Java_Printf1_print (JNIEnv* env, jclass cl, jint width, jint precision, jdouble x) { char fmt[30]; jint ret; sprintf(fmt, "%%%d.%df", width, precision); ret = printf(fmt, x); return ret; }
The function simply assembles a format string "%
w.p
f"
in the variable fmt
, then calls printf
. It then returns the number of characters it printed.
Example 10-7 shows the test program that demonstrates the Printf1
class.
Next, we want to consider how to transfer strings to and from native methods. As you know, Java strings are sequences of 16-bit Unicode characters; C strings are null-terminated strings of 8-bit characters, so strings in Java and C are quite different. The Java Native Interface has two sets of functions, one that converts Java strings to UTF (Unicode Text Format) and one that converts them to arrays of Unicode characters, that is, to jchar
arrays. The UTF format was discussed in Chapter 1—recall that ASCII characters are encoded “as is,” but all other Unicode characters are encoded as 2-byte or 3-byte sequences.
If your C code already uses Unicode, you’ll want to use the second set of conversion functions. On the other hand, if all your Java strings are restricted to ASCII characters, you can use the UTF conversion functions.
A native method with a String
parameter in its Java declaration actually receives a value of an opaque type called jstring
. A native method with a Java return value of type String
must return a value of type jstring
. JNI functions are used to read and construct these jstring
objects. For example, the NewStringUTF
function makes a new jstring
object out of a char
array that contains UTF-encoded characters. Unfortunately, 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 10-1). Therefore, you must prefix every JNI call with (*env)->
in order to actually dereference the function pointer. Furthermore, you must supply env
as the first parameter of every JNI function. This setup is somewhat cumbersome, and it could have easily been made more transparent to C programmers. We suggest that you simply supply the (*env)->
prefix without worrying about the table of function pointers.
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 paramenter 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 UTF characters that describe the character string. Note that a specific virtual machine is free to use UTF for its internal string representation, so you may get a character pointer into the actual Java string. Since Java strings are meant to be immutable, it is very important that you treat the const
seriously and do not try to write into this character array. On the other hand, if the virtual machine uses Unicode characters for its internal string representation, then this function call allocates a new memory block that will be filled with the UTF equivalents.
The virtual machine must know when you are finished using the UTF 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.
Finally, the GetStringUTFLength
function returns the number of characters needed for the UTF encoding of the string.
Let us put these functions we just described to work and write a Java equivalent of the C function sprintf
. We would like to call the function as shown in Example 10-8.
Example 10-8. Printf2Test.java
class Printf2Test { public static void main(String[] args) { double price = 44.95; double tax = 7.75; double amountDue = price * (1 + tax / 100); String s = Printf2.sprint("Amount due = %8.2f", amountDue); System.out.println(s); } }
Example 10-9 shows the class with the native sprint
method.
Example 10-9. Printf2.java
class Printf2 { public static native String sprint(String format, double x); static { System.loadLibrary("Printf2"); } }
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)
Example 10-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 10-10. Printf2.c
#include "Printf2.h" #include <string.h> #include <stdlib.h> #include <float.h> char* find_format(const char format[]) /** * @param format a string containing a printf format specifier * (such as "%8.2f"). Substrings "%%" are skipped. * @return a pointer to the format specifier (skipping the '%') * or NULL if there wasn't a unique format specifier */ { char* p; char* q; size_t n; p = strchr(format, '%'), while (p != NULL && *(p + 1) == '%') /* skip %% */ p = strchr(p + 2, '%'), if (p == NULL) return NULL; /* now check that % is unique */ p++; q = strchr(p, '%'), while (q != NULL && *(q + 1) == '%') /* skip %% */ q = strchr(q + 2, '%'), if (q != NULL) return NULL; /* % not unique */ q = p + strspn(p, " -0+#"); /* skip past flags */ q += strspn(q, "0123456789"); /* skip past field width */ if (*q == '.') { q++; q += strspn(q, "0123456789"); } /* skip past precision */ if (strchr("eEfFgG", *q) == NULL) return NULL; /* not a floating point format */ return p; } JNIEXPORT jstring JNICALL Java_Printf2_sprint (JNIEnv* env, jclass cl, jstring format, jdouble x) { const char* cformat; char* fmt; jstring ret; cformat = (*env)->GetStringUTFChars(env, format, NULL); fmt = find_format(cformat); if (fmt == NULL) ret = format; else { char* cret; int width = atoi(fmt); if (width == 0) width = DBL_DIG + 10; cret = (char*)malloc(strlen(cformat) + width); sprintf(cret, cformat, x); ret = (*env)->NewStringUTF(env, cret); free(cret); } (*env)->ReleaseStringUTFChars(env, format, cformat); return ret; }
In this function, we chose to keep the error handling simple. If the format code to print a floating-point number is not of the form %w.pc
, where c
is one of the characters e
, E
, f
, g
, or G
, then what we simply do is not format the number. You will see 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 will implement methods of the Employee
class, using native methods. Again, this is not something you would normally want to do, but it does illustrate how to access object fields from a native method when you need to do so. First, consider the raiseSalary
method. In Java, the code was simple.
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, class, fieldID); (*env)->GetXxxField(env, class, fieldID, x);
Here, class
is a value that represents a Java object of type Class
, and fieldID
is a value of a special type, jfieldID
, that denotes the offset of a field in a structure and Xxx
represents a Java data type (Object, Boolean, byte, and so on). There are two ways for obtaining the class
object. The GetObjectClass
func-tion returns the class of any object. For example:
jclass class_Employee = (*env)->GetObjectClass(env, obj_this);
The FindClass
function lets you specify the class name as a string (curiously, with / instead of periods as package name separators).
jclass class_Day = (*env)->FindClass(env, "corejava/Day");
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 will learn the complete rules for encoding signatures in the next section.
You may be thinking that accessing a data field seems quite convoluted. However, since the designers of the JNI did not want to expose the data fields directly, 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 only once the cost of computing the field offset. 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 obj_this, jdouble byPercent) { /* get the class */ jclass class_Employee = (*env)->GetObjectClass(env, obj_this); /* get the field ID */ jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, "salary", "D"); /* get the field value */ jdouble salary = (*env)->GetDoubleField(env, obj_this, id_salary); salary *= 1 + byPercent / 100; /* set the field value */ (*env)->SetDoubleField(env, obj_this, id_salary, salary); }
Accessing static fields is similar to accessing nonstatic fields. You use the GetStaticFieldID
and GetStatic
Xxx
Field/SetStatic
Xxx
Field
functions. They work almost identically to their nonstatic counterpart. There are only two differences.
Since 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 object fields and call Java methods, 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 |
|
|
|
|
|
|
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, corejava.Day)
has a signature
"(Ljava/lang/String;DLcorejava/Day;)V"
As you can see, there is no separator between the D
and Lcorejava/Day
;.
Also note that in this encoding scheme, you must use / instead of . to separate the package and class names.
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
.
You can use the javap
command with option -s
to generate the field signatures from class files. For example, run:
javap -s -private Classname
You get the following output, displaying the signatures of all fields and methods.
public synchronized class Employee extends java.lang.Object /* ACC_SUPER bit set */ { private java.lang.String name; /* Ljava/lang/String; */ private double salary; /* D */ private corejava.Day hireDay; /* Lcorejava/Day; */ public Employee(java.lang.String,double,corejava.Day); /* (Ljava/lang/String;DLcorejava/Day;)V */ public void print(); /* ()V */ public void raiseSalary(double); /* (D)V */ public int hireYear(); /* ()I */ }
There is no rational reason why programmers are forced to use this mangling scheme for describing signatures. The designers of Java could have just as easily written a function that reads Java-style signatures, 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 functions can call C functions—that is, what native methods are for. Can we go the other way and call Java code from C? 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 nonstatic 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 member function that works similarly to the C function fprintf
. That is, it should be able to print a string on an arbitrary PrintWriter
object.
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 ps.print(s)
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
, etc., 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 Chapter 1, the PrintWriter
class has nine different methods, all called print
. For that reason, you must also supply a string describing the parameters and return 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
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);
Calling static methods from native methods is similar to calling nonstatic methods. There are two differences.
You use the GetStaticMethodID
and CallStatic
Xxx
Method
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 need to find the class to use. Since 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;"
since 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 CallStaticStringMethod
function.
jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, (*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, jclass,
as well as the array types that will be 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
.
There are several variants of the JNI functions for calling 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
Xxx
Method
functions receive an implicit argument, a method ID, a class object of a superclass of the implicit argument, and explicit arguments. The function calls the version of the method in the superclass, 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
).
All Java array types have corresponding C types, as shown in the following table.
Java type |
C type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The type jarray
denotes a generic array.
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 10-2.
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)->GetObjectArray(env, array, i); (*env)->SetObjectArray(env, array, j, x);
While 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
Xxx
ArrayElements
function returns a C pointer to the starting element of the array. As with ordinary strings, you must remember to call the corresponding Release
Xxx
ArrayElements
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, since the pointer may point to a copy, any changes that you make are guaranteed to be reflected in the Java array only when you call the corresponding Release
Xxx
ArrayElements
function!
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, 0);
Whether the virtual machine actually copies the array depends on how it allocates arrays and does its garbage collection. For example, boolean
arrays are represented as packed arrays in Sun’s Java Virtual Machine, and they need to be copied into unpacked arrays of jboolean
values. 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. However, the garbage collector in the current virtual machine supplied with the JDK is not a copying collector, which means that currently non-boolean
arrays are not copied.
If you want to access a few elements of a large array, use the Get
Xxx
ArrayRegion
and Set
Xxx
ArrayRegion
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
Xxx
Array
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.
Native methods are a significant security risk to Java programs. The C run-time 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 in order to preserve the integrity of Java. In particular, when your native method diagnoses a problem that it cannot handle, then it should report this problem to the Java Virtual Machine. In Java, you would naturally throw an exception in this situation. However, C has no exceptions. Instead, you must call the Throw
or ThrowNew
function in order to create a new exception object. When the native method exits, Java will throw 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 an UTF string.
(*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 is the Java exception thrown by the virtual machine. 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 currently throw a Java exception object in your C++ code. In a C++ binding, it would be possible to implement a translation between C++ and Java exceptions—unfortunately, this is not currently implemented. You need to use SignalError
to throw a Java exception, and you need to 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 a reference to the current exception object. 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. Example 10-11 shows the code for 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.
Example 10-11. Printf4.c
#include "Printf4.h" #include <string.h> #include <stdlib.h> #include <float.h> char* find_format(const char format[]) /** * @param format a string containing a printf format specifier * (such as "%8.2f"). Substrings "%%" are skipped. * @return a pointer to the format specifier (skipping the '%') * or NULL if there wasn't a unique format specifier */ { char* p; char* q; size_t n; p = strchr(format, '%'), while (p != NULL && *(p + 1) == '%') /* skip %% */ p = strchr(p + 2, '%'), if (p == NULL) return NULL; /* now check that % is unique */ p++; q = strchr(p, '%'), while (q != NULL && *(q + 1) == '%') /* skip %% */ q = strchr(q + 2, '%'), if (q != NULL) return NULL; /* % not unique */ q = p + strspn(p, " -0+#"); /* skip past flags */ q += strspn(q, "0123456789"); /* skip past field width */ if (*q == '.') { q++; q += strspn(q, "0123456789"); } /* skip past precision */ if (strchr("eEfFgG", *q) == NULL) return NULL; /* not a floating point format */ return p; } JNIEXPORT void JNICALL Java_Printf4_fprint (JNIEnv* env, jclass cl, jobject out, jstring format, jdouble x) { const char* cformat; char* fmt; jclass class_PrintWriter; jmethodID id_print; char* cstr; int width; int i; if (format == NULL) { (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/NullPointerException"), "Printf4.fprint: format is null"); return; } cformat = (*env)->GetStringUTFChars(env, format, NULL); fmt = find_format(cformat); if (fmt == NULL) { (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/IllegalArgumentException"), "Printf4.fprint: format is invalid"); return; } width = atoi(fmt); if (width == 0) width = DBL_DIG + 10; cstr = (char*)malloc(strlen(cformat) + width); if (cstr == NULL) { (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/OutOfMemoryError"), "Printf4.fprint: malloc failed"); return; } sprintf(cstr, cformat, x); (*env)->ReleaseStringUTFChars(env, format, cformat); /* now call ps.print(str) */ /* get the class */ class_PrintWriter = (*env)->GetObjectClass(env, out); /* get the method ID */ id_print = (*env)->GetMethodID(env, class_PrintWriter, "print", "(C)V"); /* call the method */ for (i = 0; cstr[i] != 0 && !(*env)->ExceptionOccurred(env); i++) (*env)->CallVoidMethod(env, out, id_print, cstr[i]); free(cstr); }
Example 10-12. Printf4.java
import java.io.*; class Printf4 { public static native void fprint(PrintWriter ps, String format, double x); static { System.loadLibrary("Printf4"); } }
The test program in Example 10-13 demonstrates how the native method will throw an exception when the formatting string is not valid.
Example 10-13. Printf4Test.java
import java.io.*; class Printf4Test { public static void main(String[] args) { double price = 44.95; double tax = 7.75; double amountDue = price * (1 + tax / 100); PrintWriter out = new PrintWriter(System.out); Printf4.fprint(out, "Amount due = %%8.2f ", amountDue); out.flush(); } }
Up to now, we have considered Java programs that made a few C calls, presumably because C was faster or allowed access to functionality that was inaccessible from Java. Suppose you are in the opposite situation. You have a C or C++ program and would like to make a few calls to Java code, perhaps because the Java code is easier to program. Of course, you know how to call the Java methods. But you still need to add the Java Virtual Machine to your program so that the Java code can be interpreted. The so-called invocation API is used to embed the Java Virtual Machine into a C or C++ program. Here is the code that you need to initialize a virtual machine.
JavaVM *jvm; JNIEnv *env; JDK1_1InitArgs vm_args; vm_args.version = 0x00010001; JNI_GetDefaultJavaVMInitArgs(&vm_args); vm_args.classpath = getenv("CLASSPATH"); JNI_CreateJavaVM(&jvm, &env, &vm_args);
You need to set up an initialization arguments structure and set the version to Java 1.1. Call JNI_GetDefaultJavaVMInitArgs
to have the remaining fields in the initialization structure set to their defaults. Then, set the class path and call JNI_CreateJavaVM
to create the virtual machine. That call fills in a pointer jvm
to the virtual machine and a pointer env
to the execution environment.
Once you have set up the Virtual Machine, from that point on, you can call Java methods in he 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 three such functions. The most important one is the function to terminate the virtual machine:
(*jvm)->DestroyJavaVM(jvm);
The C program in Example 10-14 sets up a virtual machine and then calls the main
method of the Welcome
class which was discussed in Chapter 2 of Volume 1.
Example 10-14. InvocationTest.c
#include <jni.h> #include <stdlib.h> int main() { JavaVM *jvm; JNIEnv *env; JDK1_1InitArgs vm_args; jclass cls; jmethodID id; vm_args.version = 0x00010001; JNI_GetDefaultJavaVMInitArgs(&vm_args); vm_args.classpath = getenv("CLASSPATH"); JNI_CreateJavaVM(&jvm, &env, &vm_args); cls = (*env)->FindClass(env, "Welcome"); id = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V"); (*env)->CallStaticVoidMethod(env, cls, id, 100); (*jvm)->DestroyJavaVM(jvm); return 0; }
To compile this program under Windows, you use the command line
cl -Ic:jdkinclude -Ic:jdkincludewin32 InvocationTest.c c:jdklibjavai.lib
When running this program under Windows, make sure that your class path contains c:jdklib
classes.zip
, not just c:jdklib
. Otherwise, the class loader will not find the standard Java library.
Suppose you want to deliver this program to your customers. You need to supply the following items:
Your executable program (InvocationTest.exe
)
The class files of your Java code (Welcome.class
)
The class files of the Java standard library (classes.zip
)
The Java native code library (javai.dll
)
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. What we show you is how to put a Java 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.
For those who are not familiar with the Windows registry [1], it is a data depository that is accessed by the Windows 95 and NT operating systems, and that is available as a storage area for application programs. In older versions of Windows, the operating system as well as applications used so-called INI files to store configuration parameters. Windows 95 and NT programs are supposed to use the registry, instead. The registry has a number of advantages.
INI files store data as strings; the registry supports other data types such as integers and byte arrays.
INI file sections cannot have subsections; the registry supports a complete tree structure.
Configuration parameters were distributed over many INI files; by placing them into the registry, there is a single point for administration and backup.
Admittedly, the registry is also a single point of failure—if you mess up the registry, your computer may malfunction or even fail to boot! The sample program that we present in this section is safe, but if you plan to make any modifications to it, you should learn how to back up the registry before proceeding. See the book by Petrusha mentioned in the footnote for information on how to back up the Windows registry. (Also, programs like Norton Utilities come with user-friendly programs for registry backup.)
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 box) and type regedit
. Figure 10-3 shows the registry editor in action.
The left-hand 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-hand side shows the name/value pairs that are associated with a particular key. For example, the key
HKEY_CURRENT_USERSoftwareMicrosoftMS Setup (ACME)User Info
has two name/value pairs, namely,
DefCompany="your organization" DefName="your name"
In this case, the values are strings. The values can also be integers or arrays of bytes.
We will 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. (Following our model and the information supplied in Petrusha’s book, 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 = 0x80000000; public static final int HKEY_CURRENT_USER = 0x80000001; public static final int HKEY_LOCAL_MACHINE = 0x80000002; . . . }
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.
Here is a simple function that lists the strings that are stored with the key:
HKEY_CURRENT_USERSoftwareMicrosoftMS Setup (ACME)User Info public static void main(String[] args) { Win32RegKey key = new Win32RegKey( Win32RegKey.HKEY_CURRENT_USER, "Software\Microsoft\MS Setup (ACME)\User Info"); Enumeration enum = key.names(); while (enum.hasMoreElements()) { String name = (String)enum.nextElement(); System.out.println(name + " = " + key.getValue(name)); } }
A typical output of this program is as follows:
DefCompany = Horstmann Software DefUser = Cay Horstmann
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 and C strings and arrays. And you 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[]
. And the enumeration object needs to store the state between successive calls to hasMoreElements/nextElement
.
Let us first look at the getValue
method. The code (which is shown in Example 10-14) goes through the following steps.
Open the registry key. To read their values, the registry, API requires that keys be open.
Query the type and size of the value that is associated with the name.
Read the data into a buffer.
If the type is REG_SZ
(a string), then call NewStringUTF
to create a new string with the value data.
If the type is REG_DWORD
(a 32-bit integer), then invoke the Integer
constructor.
If the type is REG_BINARY
, then call NewByteArray
to create a new byte array and SetByteArrayRegion
to copy the value data into the byte array.
If the type is none of these, or if there was an error when calling an API function, throw an exception and carefully release all resources that had been acquired up to that point.
Close the key and return 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, it was not difficult to cope with the generic return type. The jstring
, jobject
, or jarray
reference was simply returned as a jobject
. However, the setValue
method receives a reference to an Object
, and it must determine its exact type so it can save it as either 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 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 code of the setValue
method.
Open the registry key for writing.
Find the type of the value to write.
If the type is String
, call GetStringUTFChars
to get a pointer to the characters. Also, obtain the string length.
If the type is Integer
, call the intValue
method to get the integer stored in the wrapper object.
If the type is byte[]
, call GetByteArrayElements
to get a pointer to the bytes. Also, obtain the string length.
Pass the data and length to the registry.
Close the key. If the type is String
or byte[]
, then also release the pointer to the characters or bytes.
Finally, let us turn to the native methods that enumerate keys. These are methods of the Win32RegKeyNameEnumeration
class (see Example 10-14). 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
.
As this example shows using a Java object field to store native-state data is very useful for implementing native methods.
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 object fields are initialized, and is incremented after every enumeration step.
Here , we walk through the native methods that support the enumeration. The hasMoreElements
method is simple.
Retrieve the index
and count
fields.
If the index is –1, call the startNameEnumeration
function, which opens the key, queries the count and maximum length, and initializes the hkey
, count
, maxsize
and index
fields.
Return JNI_TRUE
if index
is less than count
; JNI_FALSE
otherwise.
The nextElement
method needs to work a little harder.
Retrieve the index
and count
fields.
If the index is –1, call 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
, throw a NoSuchElementException
.
Read the next name from the registry.
Increment index
.
If index
equals count
, close the key.
To compile the program, you must link in the advapi32.lib
library. Before compiling, remember to run javah
on both Win32RegKey
and Win32RegKeyNameEnumeration.
The complete command line is
cl -Ic:jdkinclude -Ic:jdkincludewin32 -LD Win32RegKey.c advapi32.lib -FeWin32RegKey.dll
Example 10-14 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_USERSoftwareMicrosoftMS Setup (ACME)User Info
We then enumerate all names of that key and retrieve their values. The program should print out
DefName = Cay Horstmann DefCompany = Horstmann Software Default user = Bozo the clown 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 may want to use the registry editor to remove them after running this program.
Example 10-15. Win32RegKey.java
import java.util.*; public class Win32RegKey { public Win32RegKey(int theRoot, String thePath) { root = theRoot; path = thePath; } public Enumeration names() { return new Win32RegKeyNameEnumeration(root, path); } public native Object getValue(String name); public native void setValue(String name, Object value); public static final int HKEY_CLASSES_ROOT = 0x80000000; public static final int HKEY_CURRENT_USER = 0x80000001; public static final int HKEY_LOCAL_MACHINE = 0x80000002; public static final int HKEY_USERS = 0x80000003; public static final int HKEY_CURRENT_CONFIG = 0x80000005; public static final int HKEY_DYN_DATA = 0x80000006; private int root; private String path; static { System.loadLibrary("Win32RegKey"); } } class Win32RegKeyNameEnumeration implements Enumeration { Win32RegKeyNameEnumeration(int theRoot, String thePath) { root = theRoot; path = thePath; } public native Object nextElement(); public native boolean hasMoreElements(); private int root; private String path; private int index = -1; private int hkey = 0; private int maxsize; private int count; } class Win32RegKeyException extends RuntimeException { public Win32RegKeyException() {} public Win32RegKeyException(String why) { super(why); } }
Example 10-16. Win32RegKey.c
#include "Win32RegKey.h" #include "Win32RegKeyNameEnumeration.h" #include <string.h> #include <stdlib.h> #include <windows.h> JNIEXPORT jobject JNICALL Java_Win32RegKey_getValue (JNIEnv* env, jobject this_obj, jstring name) { const char* cname; jstring path; const char* cpath; HKEY hkey; DWORD type; DWORD size; jclass this_class; jfieldID id_root; jfieldID id_path; HKEY root; jobject ret; char* cret; /* get the class */ this_class = (*env)->GetObjectClass(env, this_obj); /* get the field IDs */ id_root = (*env)->GetFieldID(env, this_class, "root", "I"); id_path = (*env)->GetFieldID(env, this_class, "path", "Ljava/lang/String;"); /* get the fields */ root = (HKEY)(*env)->GetIntField(env, this_obj, id_root); path = (jstring)(*env)->GetObjectField(env, this_obj, id_path); cpath = (*env)->GetStringUTFChars(env, path, NULL); /* open the registry key */ if (RegOpenKeyEx(root, cpath, 0, KEY_READ, &hkey) != ERROR_SUCCESS) { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Open key failed"); (*env)->ReleaseStringUTFChars(env, path, cpath); return NULL; } (*env)->ReleaseStringUTFChars(env, path, cpath); cname = (*env)->GetStringUTFChars(env, name, NULL); /* find the type and size of the value */ if (RegQueryValueEx(hkey, cname, NULL, &type, NULL, &size) != ERROR_SUCCESS) { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Query value key failed"); RegCloseKey(hkey); (*env)->ReleaseStringUTFChars(env, name, cname); return NULL; } /* get memory to hold the value */ cret = (char*)malloc(size); /* read the value */ if (RegQueryValueEx(hkey, cname, NULL, &type, cret, &size) != ERROR_SUCCESS) { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Query value key failed"); free(cret); RegCloseKey(hkey); (*env)->ReleaseStringUTFChars(env, name, cname); return NULL; } /* depending on the type, store the value in a string, integer or byte array */ if (type == REG_SZ) { ret = (*env)->NewStringUTF(env, cret); } else if (type == REG_DWORD) { jclass class_Integer = (*env)->FindClass(env, "java/lang/Integer"); /* get the method ID of the constructor */ jmethodID id_Integer = (*env)->GetMethodID(env, class_Integer, "<init>", "(I)V"); int value = *(int*)cret; /* invoke the constructor */ ret = (*env)->NewObject(env, class_Integer, id_Integer, value); } else if (type == REG_BINARY) { ret = (*env)->NewByteArray(env, size); (*env)->SetByteArrayRegion(env, (jarray)ret, 0, size, cret); } else { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Unsupported value type"); ret = NULL; } free(cret); RegCloseKey(hkey); (*env)->ReleaseStringUTFChars(env, name, cname); return ret; } JNIEXPORT void JNICALL Java_Win32RegKey_setValue (JNIEnv* env, jobject this_obj, jstring name, jobject value) { const char* cname; jstring path; const char* cpath; HKEY hkey; DWORD type; DWORD size; jclass this_class; jclass class_value; jclass class_Integer; jfieldID id_root; jfieldID id_path; HKEY root; const char* cvalue; int ivalue; /* get the class */ this_class = (*env)->GetObjectClass(env, this_obj); /* get the field IDs */ id_root = (*env)->GetFieldID(env, this_class, "root", "I"); id_path = (*env)->GetFieldID(env, this_class, "path", "Ljava/lang/String;"); /* get the fields */ root = (HKEY)(*env)->GetIntField(env, this_obj, id_root); path = (jstring)(*env)->GetObjectField(env, this_obj, id_path); cpath = (*env)->GetStringUTFChars(env, path, NULL); /* open the registry key */ if (RegOpenKeyEx(root, cpath, 0, KEY_WRITE, &hkey) != ERROR_SUCCESS) { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Open key failed"); (*env)->ReleaseStringUTFChars(env, path, cpath); return; } (*env)->ReleaseStringUTFChars(env, path, cpath); cname = (*env)->GetStringUTFChars(env, name, NULL); class_value = (*env)->GetObjectClass(env, value); class_Integer = (*env)->FindClass(env, "java/lang/Integer"); /* determine the type of the value object */ if ((*env)->IsAssignableFrom(env, class_value, (*env)->FindClass(env, "java/lang/String"))) { /* it is a string--get a pointer to the characters */ cvalue = (*env)->GetStringUTFChars(env, (jstring)value, NULL); type = REG_SZ; size = (*env)->GetStringLength(env, (jstring)value) + 1; } else if ((*env)->IsAssignableFrom(env, class_value, class_Integer)) { /* it is an integer--call intValue to get the value */ jmethodID id_intValue = (*env)->GetMethodID(env, class_Integer, "intValue", "()I"); ivalue = (*env)->CallIntMethod(env, value, id_intValue); type = REG_DWORD; cvalue = (char*)&ivalue; size = 4; } else if ((*env)->IsAssignableFrom(env, class_value, (*env)->FindClass(env, "[B"))) { /* it is a byte array--get a pointer to the bytes */ type = REG_BINARY; cvalue = (char*)(*env)->GetByteArrayElements(env, (jarray)value, NULL); size = (*env)->GetArrayLength(env, (jarray)value); } else { /* we don't know how to handle this type */ (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Unsupported value type"); RegCloseKey(hkey); (*env)->ReleaseStringUTFChars(env, name, cname); return; } /* set the value */ if (RegSetValueEx(hkey, cname, 0, type, cvalue, size) != ERROR_SUCCESS) { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Query value key failed"); } RegCloseKey(hkey); (*env)->ReleaseStringUTFChars(env, name, cname); /* if the value was a string or byte array, release the pointer */ if (type == REG_SZ) { (*env)->ReleaseStringUTFChars(env, (jstring)value, cvalue); } else if (type == REG_BINARY) { (*env)->ReleaseByteArrayElements(env, (jarray)value, (byte*)cvalue, 0); } } static int startNameEnumeration(JNIEnv* env, jobject this_obj, jclass this_class) /* helper function to start enumeration of names */ { jfieldID id_index; jfieldID id_count; jfieldID id_root; jfieldID id_path; jfieldID id_hkey; jfieldID id_maxsize; HKEY root; jstring path; const char* cpath; HKEY hkey; int maxsize = 0; int count = 0; /* get the field IDs */ id_root = (*env)->GetFieldID(env, this_class, "root", "I"); id_path = (*env)->GetFieldID(env, this_class, "path", "Ljava/lang/String;"); id_hkey = (*env)->GetFieldID(env, this_class, "hkey", "I"); id_maxsize = (*env)->GetFieldID(env, this_class, "maxsize", "I"); id_index = (*env)->GetFieldID(env, this_class, "index", "I"); id_count = (*env)->GetFieldID(env, this_class, "count", "I"); /* get the field values */ root = (HKEY)(*env)->GetIntField(env, this_obj, id_root); path = (jstring)(*env)->GetObjectField(env, this_obj, id_path); cpath = (*env)->GetStringUTFChars(env, path, NULL); /* open the registry key */ if (RegOpenKeyEx(root, cpath, 0, KEY_READ, &hkey) != ERROR_SUCCESS) { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Open key failed"); (*env)->ReleaseStringUTFChars(env, path, cpath); return -1; } (*env)->ReleaseStringUTFChars(env, path, cpath); /* query count and max length of names */ if (RegQueryInfoKey(hkey, NULL, NULL, NULL, NULL, NULL, NULL, &count, &maxsize, NULL, NULL, NULL) != ERROR_SUCCESS) { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Query info key failed"); return -1; } /* set the field values */ (*env)->SetIntField(env, this_obj, id_hkey, (DWORD)hkey); (*env)->SetIntField(env, this_obj, id_maxsize, maxsize + 1); (*env)->SetIntField(env, this_obj, id_index, 0); (*env)->SetIntField(env, this_obj, id_count, count); return count; } JNIEXPORT jboolean JNICALL Java_Win32RegKeyNameEnumeration_hasMoreElements (JNIEnv* env, jobject this_obj) { jclass this_class; jfieldID id_index; jfieldID id_count; int index; int count; /* get the class */ this_class = (*env)->GetObjectClass(env, this_obj); /* get the field IDs */ id_index = (*env)->GetFieldID(env, this_class, "index", "I"); id_count = (*env)->GetFieldID(env, this_class, "count", "I"); index = (*env)->GetIntField(env, this_obj, id_index); if (index == -1) /* first time */ { count = startNameEnumeration(env, this_obj, this_class); index = 0; } else count = (*env)->GetIntField(env, this_obj, id_count); return index < count; } JNIEXPORT jobject JNICALL Java_Win32RegKeyNameEnumeration_nextElement (JNIEnv* env, jobject this_obj) { jclass this_class; jfieldID id_index; jfieldID id_hkey; jfieldID id_count; jfieldID id_maxsize; HKEY hkey; int index; int count; int maxsize; char* cret; jstring ret; /* get the class */ this_class = (*env)->GetObjectClass(env, this_obj); /* get the field IDs */ id_index = (*env)->GetFieldID(env, this_class, "index", "I"); id_count = (*env)->GetFieldID(env, this_class, "count", "I"); id_hkey = (*env)->GetFieldID(env, this_class, "hkey", "I"); id_maxsize = (*env)->GetFieldID(env, this_class, "maxsize", "I"); index = (*env)->GetIntField(env, this_obj, id_index); if (index == -1) /* first time */ { count = startNameEnumeration(env, this_obj, this_class); index = 0; } else count = (*env)->GetIntField(env, this_obj, id_count); if (index >= count) /* already at end */ { (*env)->ThrowNew(env, (*env)->FindClass(env, "java/util/NoSuchElementException"), "past end of enumeration"); return NULL; } maxsize = (*env)->GetIntField(env, this_obj, id_maxsize); hkey = (HKEY)(*env)->GetIntField(env, this_obj, id_hkey); cret = (char*)malloc(maxsize); /* find the next name */ if (RegEnumValue(hkey, index, cret, &maxsize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) { (*env)->ThrowNew(env, (*env)->FindClass(env, "Win32RegKeyException"), "Enum value failed"); free(cret); RegCloseKey(hkey); (*env)->SetIntField(env, this_obj, id_index, count); return NULL; } ret = (*env)->NewStringUTF(env, cret); free(cret); /* increment index */ index++; (*env)->SetIntField(env, this_obj, id_index, index); if (index == count) /* at end */ { RegCloseKey(hkey); } return ret; }
Example 10-17. Win32RegKeyTest.java
import java.util.*; public class Win32RegKeyTest { public static void main(String[] args) { Win32RegKey key = new Win32RegKey( Win32RegKey.HKEY_CURRENT_USER, "Software\Microsoft\MS Setup (ACME)\User Info"); key.setValue("Default user", "Bozo the clown"); key.setValue("Lucky number", new Integer(13)); key.setValue("Small primes", new byte[] { 2, 3, 5, 7, 11 }); Enumeration enum = key.names(); while (enum.hasMoreElements()) { String name = (String)enum.nextElement(); System.out.print(name + " = "); Object value = key.getValue(name); if (value instanceof byte[]) { byte[] bvalue = (byte[])value; for (int i = 0; i < bvalue.length; i++) System.out.print((bvalue[i] & 0xFF) + " "); } else System.out.print(value); System.out.println(); } } }
Microsoft has a method it calls the Raw Native Interface (http://www.microsoft.com/java) that is actually fairly close to the method used for calling native functions in the JDK 1.0. As far as we can tell, it has no special virtues; we wouldn’t bother with it.
On the other hand, Microsoft and Apple both have announced another approach to calling native methods called JDirect. (www.microsoft.com/java and http://applejava.apple.com/MRJGoodies) Both approaches are based on special virtual machines that handle a lot more of the busy work needed to call native methods than does the standard JVM in the JDK.
In particular, both versions of JDirect would allow you to dispense with the stubs produced by javah
and to access native methods contained in a DLL without using any C code whatsoever. The JDirect specification is in flux, but it is potentially very interesting. (Keep in mind that using it ties you to using Microsoft’s virtual machine or Apple’s MRJ.)
For example, Microsoft’s version of JDirect essentially makes calling a Windows-based DLL trivial. Based on the current specification, your code for accessing the Registry
function under JDirect could be as simple as:
class Registry { /** @dll.import("advapi32") */ private static native int RegCreateKey(int hkey, String subKey, int result);} //other registry functions follow }
where the @dll.import
will tell Microsoft’s JVM to automatically make whatever type conversions need to be made.
Based on the limited information at hand, the Apple version would be something like:
import com.apple.NativeObject; public class MakeBeeps implements NativeObject { public native static void SysBeep(short duration); private static String[] nativeNames = { "InterfaceLib" }; }
Both of these methods are clearly much simpler than the methods used in the JDK, but whether they will be uniformly adopted by all virtual machines on Windows or on the Apple is exceedingly doubtful. If you use Java as a nice programming language for platform-specific programming, you will find these direct call interfaces quite interesting. If you use Java to write platform-independent code, you will likely pass on them.
18.188.242.157