- 論壇徽章:
- 0
|
這里給出的是Core Java 2 Volume IIAdvanced Features 5th Edition中關(guān)于JNI一章的內(nèi)容,其它的參考資料可以看SUN的官方網(wǎng)站的docs和Tutorial,地址在我的鏈接里面有,除此之外還有g(shù)oogle,baidu等工具
//文中所有的文本內(nèi)部鏈接復(fù)制過來應(yīng)該都沒有用。
Chapter 11. Native Methods
We hope that you are convinced that code written in the Java programming language 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 with the Java programming language than with C or C++.
Multithreading is probably easier to code in the Java programming language than in most other languages.
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 the Java programming language 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 technology 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 (JIT) compilation, intensive computations coded in the Java programming language 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 native code from programs written in the Java programming language. 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 technology comes with hooks for working with system libraries, and the JDK has a few tools to relieve some (but not all) of the programming tedium.
NOTE
![]()
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 binding between the Java and FORTRAN programming languages.
Still, keep in mind: If you use native methods, 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 code that uses native method libraries. For that reason, many potential users will be reluctant to use programs in the Java programming language that require native code. Aside from the security issue, native libraries are unlikely to be as safe as code written in the Java programming language, especially if they are written in a language like C or C++ that offers no protection against overwriting 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 a serial port, in a 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 the Java programming language? If you are concerned about efficiency, benchmark a Java platform implementation. In most cases, the speed using a just-in-time compiler will be sufficient. A talk 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. 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.
NOTE
![]()
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 was used by the Microsoft Virtual Machine. Sun Microsystems has assured developers that the JNI binding described here is a permanent part of the Java platform, 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 native methods. In particular, you'll see how to make the correspondence between Java data types, feature names, and function calls and those of C. (This correspondence is usually called the C binding.)
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 direct correspondence between Java platform classes and those in C++
Calling a C Function from the Java Programming Language
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 the Java programming language. 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 programs. The Java programming language 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 a program, then the virtual machine 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 platform call and the actual C function. The stub does this translation by taking parameter information off the virtual machine stack and passing it to the compiled C function.
Create a special shared library and export the stub from it.
Use a special method, called System.loadLibrary, to tell the Java run-time environment 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—platform-dependent functions that are obviously not available directly from the Java platform.
Working with the printf Function
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 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. 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. 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 runtime environment expects. Here are the rules:
Use the full Java method name, such as HelloNative.greeting. If the class is in a package, then prepend the package name, such as com.horstmann.HelloNative.greeting.
Replace every period with an underscore, and append the prefix Java_. For example, Java_HelloNative_greeting or Java_com_horstmann_HelloNative_greeting.
If the class name contains characters that are not ASCII letters or digits—that is, '_', '$', or Unicode characters with code > '\u007F'—replace them with _0xxxx, where xxxx is the sequence of four hexadecimal digits of the character's Unicode value.
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 should run the javah utility, which automatically generates the function names. To use javah, first, compile the source file (given in
Example 11-3
).
javac HelloNative.java
Next, call the javah utility to produce a C header file. The javah executable can be found in the jdk/bin directory.
javah HelloNative
NOTE
![]()
If you are still using JDK1.1, be sure to use the -jni flag when running javah. Without that flag, the javah tool in JDK 1.1 generates the header file for the Java 1.0 binding. Starting with JDK 1.2, the JNI binding is the default.
Using javah creates a header file, HelloNative.h, as in
Example 11-1
.
Example 11-1 HelloNative.h 1. /* DO NOT EDIT THIS FILE - it is machine generated */
2. #include
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 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.)
Now, you simply have to copy the function prototype from the header file into the source file and give the implementation code for the function, as shown in
Example 11-2
.
Example 11-2 HelloNative.c1. #include "HelloNative.h"
2. #include
3.
4. JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv* env,
5. jclass cl)
6.{
7. printf("Hello world!\n");
8.}
In this simple function, we ignore the env and cl arguments. You'll see their use later.
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". For example,
#include "HelloNative.h"
#include
extern "C"
JNIEXPORT void JNICALL Java_HelloNative_greeting
(JNIEnv* env, jclass cl)
{
printf("Hello, Native World!\n");
}
Next, compile the 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 -c -fPIC -I/usr/local/jdk/include/
-I/jdk/include/linux HelloNative.c
gcc -shared -o libHelloNative.so HelloNative.o
With the Sun compiler under the Solaris operating environment, the command to use is
cc -G -I/usr/local/jdk/include
-I/usr/local/jdk/include/solaris
HelloNative.c -o libHelloNative.so
With the Microsoft C++ compiler under Windows, the command you use is
cl -Ic:\jdk\include -Ic:\jdk\include\win32 -LD HelloNative.c
-FeHelloNative.dll
You can also use the freely available Cygwin programming environment from
http://www.cygwin.com
. It contains the Gnu C compiler and libraries for UNIX-style programming on Windows. With Cygwin, use the commands
gcc -c -D__int64="long long"
-Ic:/jdk/include/ -Ic:/jdk/include/win32 HelloNative.c
dllwrap --add-stdcall-alias -o HelloNative.dll HelloNative.o
You may need to use different paths to specify the locations of the header files, depending on which directory contains the SDK.
TIP
![]()
If you use the Microsoft C++ compiler to compile DLLs from a DOS shell, first run the batch file
c:\devstudio\vc\bin\vcvars32.bat
That batch file properly configures the command-line compiler by setting up the path and the environment variables needed by the compiler.
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 may 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, we need to add the call to the System.loadLibrary method that ensures that the virtual machine 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 11-3
:
Example 11-3 HelloNative.java1. class HelloNative
2. {
3. public static native void greeting();
4.
5. static
6. {
7. System.loadLibrary("HelloNative");
8. }
9. }
Assuming you have followed all the steps given above, you are now ready to run the HelloNativeTest application shown in
Example 11-4
.
Example 11-4 HelloNativeTest.java1. class HelloNativeTest
2. {
3. public static void main(String[] args)
4. {
5. HelloNative.greeting();
6. }
7. }
If you compile and run this 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 programming language code, you will see that we have taken the first steps toward bridging the gap between the two languages!
NOTE
![]()
Some shared libraries for native code need to run initialization code. You can place any initialization code into a JNI_OnLoad method. Similarly, when the 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_1.
java.lang.System
![]()
void loadLibrary(String libname)
loads the library with the given name. The library is located in the library search path. The exact method for locating the library is operating-system dependent. Under Windows, this method searches first the current directory, then the directories listed in the PATH environment variable.
void load(String filename)
loads the library with the given file name. If the library is not found, then an UnsatisfiedLinkError is thrown.
Numeric Parameters and Return Values
When passing numbers between C and the Java programming language, you need to understand which types correspond to each other. 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 the Java platform, 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 11-1
shows the correspondence between Java types and C types.
Table 11-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
Recall that the Java library 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 11-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 11-5 Printf1.java 1. class Printf1
2. {
3. public static native int print(int width, int precision,
4. double x);
5.
6. static
7. {
8. System.loadLibrary("Printf1");
9. }
10. }
Notice that when implementing the method in C, all int and double parameters are changed to jint and jdouble, as shown in
Example 11-6
.
Example 11-6 Printf1.c 1. #include "Printf1.h"
2. #include
3.
4. JNIEXPORT jint JNICALL Java_Printf1_print
5. (JNIEnv* env, jclass cl, jint width, jint precision, jdouble x)
6. {
7. char fmt[30];
8. jint ret;
9. sprintf(fmt, "%%%d.%df", width, precision);
10. ret = printf(fmt, x);
11. fflush(stdout);
12. return ret;
13. }
The function simply assembles a format string "%w.pf" in the variable fmt, then calls printf. It then returns the number of characters printed.
Example 11-7
shows the test program that demonstrates the Printf1 class.
Example 11-7 Printf1Test.java 1. class Printf1Test
2. {
3. public static void main(String[] args)
4. {
5. int count = Printf1.print(8, 4, 3.14);
6. count += Printf1.print(8, 4, (double)count);
7. System.out.println();
8. for (int i = 0; i
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 16-bit Unicode characters; C strings are null-terminated strings of 8-bit characters, so strings are quite different in the two languages. 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 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 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\n";
jstr = (*env)->NewStringUTF(env, greeting);
return jstr;
}
All calls to JNI functions use the env pointer that is the first argument of every native method. The env pointer is a pointer to a table of function pointers (see
Figure 11-1
). Therefore, you must prefix every JNI call with (*env)-> to actually dereference the function pointer. Furthermore, env is the first parameter of every JNI function. 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.
Figure 11-1. The env pointer
![]()
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 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.
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 UTF encoding of the string.
Accessing Java strings from C code
![]()
jstring NewStringUTF(JNIEnv* env, const char bytes[])
returns a new Java string object from an UTF string, or NULL if the string cannot be constructed.
Parameters:
env
the JNI interface pointer
bytes
the null-terminated UTF string
jsize GetStringUTFLength(JNIEnv* env, jstring string)
returns the number of characters required for the UTF encoding.
Parameters:
env
the JNI interface pointer
string
a Java string object
const jbyte* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* isCopy)
returns a pointer to the UTF encoding of a string, or NULL if the character array cannot be constructed. The pointer is valid until ReleaseStringUTFChars is called.
Parameters:
env
the JNI interface pointer
string
a Java string object
isCopy
points to a jboolean that is filled with JNI_TRUE if a copy is made; with JNI_FALSE otherwise
void ReleaseStringUTFChars(JNIEnv* env, jstring string, const jbyte bytes[])
informs the virtual machine that the native code no longer needs access to the Java string through bytes.
Parameters:
env
the JNI interface pointer
string
a Java string object
bytes
a pointer returned by GetStringUTFChars
void GetStringRegion(JNIEnv *env, jstring string, jsize start, jsize length, jchar *buffer)
copies a sequence of Unicode characters from a string to a user-supplied buffer.
Parameters:
env
the JNI interface pointer
string
a Java string object
start
the starting index
length
the number of characters to copy
buffer
the user-supplied buffer
void GetStringUTFRegion(JNIEnv *env, jstring string, jsize start, jsize length, jbyte *buffer)
copies a sequence of UTF8 characters from a string to a user-supplied buffer. The buffer must be long enough to hold the bytes. In the worst case, 3 X length bytes are copied.
jstring NewString(JNIEnv* env, const jchar chars[], jsize length)
returns a new Java string object from a Unicode string, or NULL if the string cannot be constructed.
Parameters:
env
the JNI interface pointer
chars
the null-terminated UTF string
length
the number of characters in the string
jsize GetStringLength(JNIEnv* env, jstring string)
returns the number of characters in the string.
Parameters:
env
the JNI interface pointer
string
a Java string object
const jchar* GetStringChars(JNIEnv* env, jstring string, jboolean* isCopy)
returns a pointer to the Unicode encoding of a string, or NULL if the character array cannot be constructed. The pointer is valid until ReleaseStringChars is called.
Parameters:
env
the JNI interface pointer
string
a Java string object
isCopy
is either NULL or points to a jboolean that is filled with JNI_TRUE if a copy is made; with JNI_FALSE otherwise
void ReleaseStringChars(JNIEnv* env, jstring string, const jchar chars[])
informs the virtual machine that the native code no longer needs access to the Java string through chars.
Parameters:
env
the JNI interface pointer
string
a Java string object
chars
a pointer returned by GetStringChars
Calling sprintf in a Native Method
Let us put these functions we just described to work and write a class that calls the C function sprintf. We would like to call the function as shown in
Example 11-8
.
Example 11-8 Printf2Test.java 1. class Printf2Test
2. {
3. public static void main(String[] args)
4. {
5. double price = 44.95;
6. double tax = 7.75;
7. double amountDue = price * (1 + tax / 100);
8.
9. String s = Printf2.sprint("Amount due = %8.2f", amountDue);
10. System.out.println(s);
11. }
12. }
Example 11-9
shows the class with the native sprintf method.
Example 11-9 Printf2.java1. class Printf2
2. {
3. public static native String sprint(String format, double x);
4.
5. static
6. {
7. System.loadLibrary("Printf2");
8. }
9. }
Therefore, the C function that formats a floating-point number has the prototype
JNIEXPORT jstring JNICALL Java_Printf2_sprint
(JNIEnv* env, jclass cl, jstring format, jdouble x)
Example 11-10
shows the code for the C implementation. Note the calls to GetStringUTFChars to read the format argument, NewStringUTF to generate the return value, and ReleaseStringUTFChars to inform the virtual machine that access to the string is no longer required.
Example 11-10 Printf2.c 1. #include "Printf2.h"
2. #include
3. #include
4. #include
5.
6. /**
7. @param format a string containing a printf format specifier
8. (such as "%8.2f"). Substrings "%%" are skipped.
9. @return a pointer to the format specifier (skipping the '%')
10. or NULL if there wasn't a unique format specifier
11. */
12. char* find_format(const char format[])
13. {
14. char* p;
15. char* q;
16.
17. p = strchr(format, '%');
18. while (p != NULL && *(p + 1) == '%') /* skip %% */
19. p = strchr(p + 2, '%');
20. if (p == NULL) return NULL;
21. /* now check that % is unique */
22. p++;
23. q = strchr(p, '%');
24. while (q != NULL && *(q + 1) == '%') /* skip %% */
25. q = strchr(q + 2, '%');
26. if (q != NULL) return NULL; /* % not unique */
27. q = p + strspn(p, " -0+#"); /* skip past flags */
28. q += strspn(q, "0123456789"); /* skip past field width */
29. if (*q == '.') { q++; q += strspn(q, "0123456789"); }
30. /* skip past precision */
31. if (strchr("eEfFgG", *q) == NULL) return NULL;
32. /* not a floating point format */
33. return p;
34. }
35.
36. JNIEXPORT jstring JNICALL Java_Printf2_sprint(JNIEnv* env,
37. jclass cl, jstring format, jdouble x)
38. {
39. const char* cformat;
40. char* fmt;
41. jstring ret;
42.
43. cformat = (*env)->GetStringUTFChars(env, format, NULL);
44. fmt = find_format(cformat);
45. if (fmt == NULL)
46. ret = format;
47. else
48. {
49. char* cret;
50. int width = atoi(fmt);
51. if (width == 0) width = DBL_DIG + 10;
52. cret = (char*)malloc(strlen(cformat) + width);
53. sprintf(cret, cformat, x);
54. ret = (*env)->NewStringUTF(env, cret);
55. free(cret);
56. }
57. (*env)->ReleaseStringUTFChars(env, format, cformat);
58. return ret;
59. }
In this function, we chose to keep the error handling simple. If the format code to print a floating-point number is not of the form %w.pc, where c is one of the characters e, E, f, g, or G, then what we simply do is not format the number. You will see later how to make a native method throw an exception.
Accessing Object 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 will implement a method of the Employee class that was introduced in Chapter 4 of Volume 1, using a native method. 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.
Consider the raiseSalary method. In the Java programming language, 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 identifies 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 function 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_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 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 identifier.
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);
}
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 need to 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);
Examples 11-11
and
11-12
show the Java code for a test program and the Employee class.
Example 11-13
contains the C code for the native raiseSalary method.
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. 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);
Example 11-11 EmployeeTest.java 1. public class EmployeeTest
2. {
3. public static void main(String[] args)
4. {
5. Employee[] staff = new Employee[3];
6.
7. staff[0] = new Employee("Harry Hacker", 35000);
8. staff[1] = new Employee("Carl Cracker", 75000);
9. staff[2] = new Employee("Tony Tester", 38000);
10.
11. int i;
12. for (i = 0; i
Example 11-12 Employee.java 1. public class Employee
2. {
3. public Employee(String n, double s)
4. {
5. name = n;
6. salary = s;
7. }
8.
9. public native void raiseSalary(double byPercent);
10.
11. public void print()
12. {
13. System.out.println(name + " " + salary);
14. }
15.
16. private String name;
17. private double salary;
18.
19. static
20. {
21. System.loadLibrary("Employee");
22. }
23. }
Example 11-13 Employee.c 1. #include "Employee.h"
2.
3. #include
4.
5. JNIEXPORT void JNICALL Java_Employee_raiseSalary(JNIEnv* env,
6. jobject obj_this, jdouble byPercent)
7. {
8. /* get the class */
9. jclass class_Employee = (*env)->GetObjectClass(env,
10. obj_this);
11.
12. /* get the field ID */
13. jfieldID id_salary = (*env)->GetFieldID(env,
14. class_Employee, "salary", "D");
15.
16. /* get the field value */
17. jdouble salary = (*env)->GetDoubleField(env, obj_this,
18. id_salary);
19.
20. salary *= 1 + byPercent / 100;
21.
22. /* set the field value */
23. (*env)->SetDoubleField(env, obj_this, id_salary, salary);
24. }
Accessing object fields
![]()
jfieldID GetFieldID(JNIEnv *env, jclass cl, const char ame[], const char sig[])
returns the identifier of a field in a class.
Parameters:
env
the JNI interface pointer
cl
the class object
name
the field name
sig
the encoded field signature
Xxx GetXxxField(JNIEnv *env, jobject obj, jfieldID id)
returns the value of a field. The field type Xxx is one of Object, Boolean, Byte, Char, Short, Int, Long, Float, or Double.
Parameters:
env
the JNI interface pointer
obj
the object whose field is being returned
id
the field identifier
void SetXxxField(JNIEnv *env, jobject obj, jfieldID id, Xxx value)
sets a field to a new value. The field type Xxx is one of Object, Boolean, Byte, Char, Short, Int, Long, Float, or Double.
Parameters:
env
the JNI interface pointer
obj
the object whose field is being set
id
the field identifier
value
the new field value
jfieldID GetStaticFieldID(JNIEnv *env, jclass cl, const char name[], const char sig[])
returns the identifier of a static field in a class.
Parameters:
env
the JNI interface pointer
cl
the class object
name
the field name
sig
the encoded field signature
Xxx GetStaticXxxField(JNIEnv *env, jclass cl, jfieldID id)
returns the value of a static field. The field type Xxx is one of Object, Boolean, Byte, Char, Short, Int, Long, Float, or Double.
Parameters:
env
the JNI interface pointer
cl
the class object whose static field is being set
id
the field identifier
void SetStaticXxxField(JNIEnv *env, jclass cl, jfieldID id, Xxx value)
sets a static field to a new value. The field type Xxx is one of Object, Boolean, Byte, Char, Short, Int, Long, Float, or Double.
Parameters:
env
the JNI interface pointer
cl
the class object whose static field is being set
id
the field identifier
value
the new field value
Signatures
To access object 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
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"
As you can see, 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.
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.
TIP
![]()
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 java.util.Date hireDay;
/* Ljava/util/Date; */
public Employee(java.lang.String,double,java.util.Date);
/* (Ljava/lang/String;DLjava/util/Date;)V */
public void print();
/* ()V */
public void raiseSalary(double);
/* (D)V */
public int hireYear();
/* ()I */
}
NOTE
![]()
There is no rational reason why programmers are forced 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 nonstatic methods, and then we show you how to do it for static methods.
Nonstatic 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 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, 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);
Examples 11-14
and
11-15
show the Java code for a test program and the Printf3 class.
Example 11-16
contains the C code for the native fprint method.
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 nonstatic 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 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 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
![]()
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.
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 "" 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, "", "(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
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 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 11-14 Printf3Test.java 1. import java.io.*;
2.
3. class Printf3Test
4. {
5. public static void main(String[] args)
6. {
7. double price = 44.95;
8. double tax = 7.75;
9. double amountDue = price * (1 + tax / 100);
10. PrintWriter out = new PrintWriter(System.out);
11. Printf3.fprint(out, "Amount due = %8.2f\n", amountDue);
12. out.flush();
13. }
14. }
Example 11-15 Printf3.java 1. import java.io.*;
2.
3. class Printf3
4. {
5. public static native void fprint(PrintWriter out,
6. String format, double x);
7.
8. static
9. {
10. System.loadLibrary("Printf3");
11. }
12. }
Example 11-16 Printf3.c 1. #include "Printf3.h"
2. #include
3. #include
4. #include
5.
6. /**
7. @param format a string containing a printf format specifier
8. (such as "%8.2f"). Substrings "%%" are skipped.
9. @return a pointer to the format specifier (skipping the '%')
10. or NULL if there wasn't a unique format specifier
11. */
12. char* find_format(const char format[])
13. {
14. char* p;
15. char* q;
16.
17. p = strchr(format, '%');
18. while (p != NULL && *(p + 1) == '%') /* skip %% */
19. p = strchr(p + 2, '%');
20. if (p == NULL) return NULL;
21. /* now check that % is unique */
22. p++;
23. q = strchr(p, '%');
24. while (q != NULL && *(q + 1) == '%') /* skip %% */
25. q = strchr(q + 2, '%');
26. if (q != NULL) return NULL; /* % not unique */
27. q = p + strspn(p, " -0+#"); /* skip past flags */
28. q += strspn(q, "0123456789"); /* skip past field width */
29. if (*q == '.') { q++; q += strspn(q, "0123456789"); }
30. /* skip past precision */
31. if (strchr("eEfFgG", *q) == NULL) return NULL;
32. /* not a floating point format */
33. return p;
34. }
35.
36. JNIEXPORT void JNICALL Java_Printf3_fprint(JNIEnv* env,
37. jclass cl, jobject out, jstring format, jdouble x)
38. {
39. const char* cformat;
40. char* fmt;
41. jstring str;
42. jclass class_PrintWriter;
43. jmethodID id_print;
44.
45. cformat = (*env)->GetStringUTFChars(env, format, NULL);
46. fmt = find_format(cformat);
47. if (fmt == NULL)
48. str = format;
49. else
50. {
51. char* cstr;
52. int width = atoi(fmt);
53. if (width == 0) width = DBL_DIG + 10;
54. cstr = (char*)malloc(strlen(cformat) + width);
55. sprintf(cstr, cformat, x);
56. str = (*env)->NewStringUTF(env, cstr);
57. free(cstr);
58. }
59. (*env)->ReleaseStringUTFChars(env, format, cformat);
60.
61. /* now call ps.print(str) */
62.
63. /* get the class */
64. class_PrintWriter = (*env)->GetObjectClass(env, out);
65.
66. /* get the method ID */
67. id_print = (*env)->GetMethodID(env, class_PrintWriter,
68. "print", "(Ljava/lang/String;)V");
69.
70. /* call the method */
71. (*env)->CallVoidMethod(env, out, id_print, str);
72. }
Executing Java methods from C code
![]()
jmethodID GetMethodID(JNIEnv *env, jclass cl, const char name[], const char sig[])
returns the identifier of a method in a class.
Parameters:
env
the JNI interface pointer
cl
the class object
name
the method name
sig
the encoded method signature
void CallXxxMethod(JNIEnv *env, jobject obj, jmethodID id, args)
void CallXxxMethodA(JNIEnv *env, jobject obj,jmethodID id, jvalue args[])
void CallXxxMethodV(JNIEnv *env, jobject obj, jmethodID id, va_list args)
These functions call a method. The return type Xxx is one of Object, Boolean, Byte, Char, Short, Int, Long, Float, or Double. The first function has a variable number of arguments—simply append the method parameters after the method ID. The second function receives the method arguments in an array of jvalue, where jvalue is a union defined as
typedef union jvalue
{
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
The third function receives the method parameters in a va_list, as defined in the C header stdarg.h.
Parameters:
env
the JNI interface pointer
obj
the implicit argument of the method
id
the method identifier
args
the method arguments
void CallNonvirtualXxxMethod(JNIEnv *env, jobject obj, jclass cl, jmethodID id, args)
void CallNonvirtualXxxMethodA(JNIEnv *env, jobject obj, jclass cl, jmethodID id, jvalue args[])
void CallNonvirtualXxxMethodV(JNIEnv *env, jobject obj, jclass cl, jmethodID id, va_list args)
These functions call a method, bypassing dynamic dispatch. The return type Xxx is one of Object, Boolean, Byte, Char, Short, Int, Long, Float, or Double. The first function has a variable number of arguments—simply append the method parameters after the method ID. The second function receives the method arguments in an array of jvalue. The third function receives the method parameters in a va_list, as defined in the C header stdarg.h.
Parameters:
env
the JNI interface pointer
obj
the implicit argument of the method
cl
the class whose implementation of the method is tobe called
id
the method identifier
args
the method arguments
jmethodID GetStaticMethodID(JNIEnv *env, jclass cl, const char name[], const char sig[])
returns the identifier of a static method in a class.
Parameters:
env
the JNI interface pointer
cl
the class object
name
the method name
sig
the encoded method signature
void CallStaticXxxMethod(JNIEnv *env, jclass cl, jmethodID id, args)
void CallStaticXxxMethodA(JNIEnv *env, jclass cl, jmethodID id, jvalue args[])
void CallStaticXxxMethodV(JNIEnv *env, jclass cl, jmethodID id, va_list args)
These functions call a static method. The return type Xxx is one of Object, Boolean, Byte, Char, Short, Int, Long, Float, or Double. The first function has a variable number of arguments—simply append the method parameters after the method ID. The second function receives the method arguments in an array of jvalue. The third function receives the method parameters in a va_list, as defined in the C header stdarg.h.
Parameters:
env
the JNI interface pointer
cl
the class of the static method
id
the method identifier
args
the method arguments
jobject NewObject(JNIEnv *env, jclass cl, jmethodID id, args)
jobject NewObjectA(JNIEnv *env, jclass cl, jmethodID id, jvalue args[])
jobject NewObjectV(JNIEnv *env, jclass cl, jmethodID id, va_list args)
These functions call a constructor. The method ID is obtained from GetMethodID with a method name of "" and a return type of void. The first function has a variable number of arguments—simply append the method parameters after the method ID. The second function receives the method arguments in an array of jvalue. The third function receives the method parameters in a va_list, as defined in the C header stdarg.h.
Parameters:
env
the JNI interface pointer
cl
the class to be instantiated
id
the constructor method identifier
args
the constructor arguments
Arrays
All array types of the Java programming language have corresponding C types, as shown in
Table 11-2
.
Table 11-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
The type jarray denotes a generic array.
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 11-2
.
Figure 11-2. 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);
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 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, since the pointer may 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
![]()
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.
jdoubleArray array_a = ...;
double scaleFactor = ...;
double* a = (*env)->GetDoubleArrayElements(env, array_a, NULL);
for (i = 0; i GetArrayLength(env, array_a); i++)
a = a * 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. 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 SDK is not a copying collector, which means that arrays are pinned.
NOTE
![]()
In the Sun JVM implementation, boolean arrays are represented as packed arrays, and the GetBooleanArrayElements method copies them into unpacked arrays of jboolean values.
If you want to access 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
![]()
SDK 1.4 adds 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.
Manipulating Java Arrays in C code
![]()
jsize GetArrayLength(JNIEnv *env, jarray array)
returns the number of elements in the array.
Parameters:
env
the JNI interface pointer
array
the array object
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index)
returns the value of an array element.
Parameters:
env
the JNI interface pointer
array
the array object
index
the array offset
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value)
sets an array element to a new value.
Parameters:
env
the JNI interface pointer
array
the array object
index
the array offset
value
the new value
Xxx* GetXxxArrayElements(JNIEnv *env, jarray array, jboolean* isCopy)
yields a C pointer to the elements of a Java array. The field type Xxx is one of Boolean, Byte, Char, Short, Int, Long, Float, or Double. The pointer must be passed to ReleaseXxxArrayElements when it is no longer needed.
Parameters:
env
the JNI interface pointer
array
the array object
isCopy
is either NULL or points to a jboolean that is filled with JNI_TRUE if a copy is made; with JNI_FALSE otherwise
void ReleaseXxxArrayElements(JNIEnv *env, jarray array, Xxx elems[], jint mode)
notifies the virtual machine that a pointer obtained by GetXxxArrayElements is no longer needed.
Parameters:
env
the JNI interface pointer
array
the array object
elems
the pointer to the array elements that is no longer needed
mode
0 = frees the elems buffer after updating array elements
JNI_COMMIT = do not free the elems buffer after updating the array elements
JNI_ABORT = free the elems buffer without updating the array elements
void GetXxxArrayRegion(JNIEnv *env, jarray array, jint start, jint length, Xxx elems[])
copies elements from a Java array to a C array. The field type Xxx is one of Boolean, Byte, Char, Short, Int, Long, Float, or Double.
Parameters:
env
the JNI interface pointer
array
the array object
start
the starting index
length
the number of elements to copy
elems
the C array that holds the elements
void SetXxxArrayRegion(JNIEnv *env, jarray array, jint start, jint length, Xxx elems[])
copies elements from a C array to a Java array. The field type Xxx is one of Boolean, Byte, Char, Short, Int, Long, Float, or Double.
Parameters:
env
the JNI interface pointer
array
the array object
start
the starting index
length
the number of elements to copy
elems
the C array that holds the elements
Error Handling
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, then 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 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,
"", "()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 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 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
![]()
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 exceptions in the C++ and Java programming languages—however, this is not currently implemented. You need to use Throw or ThrowNew to throw a Java exception in a native C++ method, 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 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
jbool 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.
Example 11-17
shows the code for the native method, and
Example 11-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
Example 11-19
demonstrates how the native method throws an exception when the formatting string is not valid.
Example 11-17 Printf4.c 1. #include "Printf4.h"
2. #include
3. #include
4. #include
5.
6. /**
7. @param format a string containing a printf format specifier
8. (such as "%8.2f"). Substrings "%%" are skipped.
9. @return a pointer to the format specifier (skipping the '%')
10. or NULL if there wasn't a unique format specifier
11. */
12. char* find_format(const char format[])
13. {
14. char* p;
15. char* q;
16.
17. p = strchr(format, '%');
18. while (p != NULL && *(p + 1) == '%') /* skip %% */
19. p = strchr(p + 2, '%');
20. if (p == NULL) return NULL;
21. /* now check that % is unique */
22. p++;
23. q = strchr(p, '%');
24. while (q != NULL && *(q + 1) == '%') /* skip %% */
25. q = strchr(q + 2, '%');
26. if (q != NULL) return NULL; /* % not unique */
27. q = p + strspn(p, " -0+#"); /* skip past flags */
28. q += strspn(q, "0123456789"); /* skip past field width */
29. if (*q == '.') { q++; q += strspn(q, "0123456789"); }
30. /* skip past precision */
31. if (strchr("eEfFgG", *q) == NULL) return NULL;
32. /* not a floating point format */
33. return p;
34. }
35.
36. JNIEXPORT void JNICALL Java_Printf4_fprint(JNIEnv* env,
37. jclass cl, jobject out, jstring format, jdouble x)
38. {
39. const char* cformat;
40. char* fmt;
41. jclass class_PrintWriter;
42. jmethodID id_print;
43. char* cstr;
44. int width;
45. int i;
46.
47. if (format == NULL)
48. {
49. (*env)->ThrowNew(env,
50. (*env)->FindClass(env,
51. "java/lang/NullPointerException"),
52. "Printf4.fprint: format is null");
53. return;
54. }
55.
56. cformat = (*env)->GetStringUTFChars(env, format, NULL);
57. fmt = find_format(cformat);
58.
59. if (fmt == NULL)
60. {
61. (*env)->ThrowNew(env,
62. (*env)->FindClass(env,
63. "java/lang/IllegalArgumentException"),
64. "Printf4.fprint: format is invalid");
65. return;
66. }
67.
68. width = atoi(fmt);
69. if (width == 0) width = DBL_DIG + 10;
70. cstr = (char*)malloc(strlen(cformat) + width);
71.
72. if (cstr == NULL)
73. {
74. (*env)->ThrowNew(env,
75. (*env)->FindClass(env, "java/lang/OutOfMemoryError"),
76. "Printf4.fprint: malloc failed");
77. return;
78. }
79.
80. sprintf(cstr, cformat, x);
81.
82. (*env)->ReleaseStringUTFChars(env, format, cformat);
83.
84. /* now call ps.print(str) */
85.
86. /* get the class */
87. class_PrintWriter = (*env)->GetObjectClass(env, out);
88.
89. /* get the method ID */
90. id_print = (*env)->GetMethodID(env, class_PrintWriter,
91. "print", "(C)V");
92.
93. /* call the method */
94. for (i = 0; cstr != 0 && !(*env)->ExceptionOccurred(env);
95. i++)
96. (*env)->CallVoidMethod(env, out, id_print, cstr);
97.
98. free(cstr);
99.}
Example 11-18 Printf4.java 1. import java.io.*;
2.
3. class Printf4
4. {
5. public static native void fprint(PrintWriter ps,
6. String format, double x);
7.
8. static
9. {
10. System.loadLibrary("Printf4");
11. }
12. }
Example 11-19 Printf4Test.java 1. import java.io.*;
2.
3. class Printf4Test
4. {
5. public static void main(String[] args)
6. {
7. double price = 44.95;
8. double tax = 7.75;
9. double amountDue = price * (1 + tax / 100);
10. PrintWriter out = new PrintWriter(System.out);
11. /* This call will throw an exception--note the %% */
12. Printf4.fprint(out, "Amount due = %%8.2f\n", amountDue);
13. out.flush();
14. }
15. }
Error handling in C code
![]()
jint Throw(JNIEnv *env, jthrowable obj)
prepares an exception to be thrown upon exiting from the native code. Returns 0 on success, a negative value on failure.
Parameters:
env
the JNI interface pointer
obj
the exception object to throw
jint ThrowNew(JNIEnv *env, jclass clazz, const char msg[])
prepares an exception to be thrown upon exiting from the native code. Returns 0 on success, a negative value on failure.
Parameters:
env
the JNI interface pointer
cl
the class of the exception object to throw
msg
a UTF string denoting the String construction argument of the exception object
jthrowable ExceptionOccurred(JNIEnv *env)
returns the exception object if an exception is pending, or NULL otherwise.
Parameters:
env
the JNI interface pointer
jboolean ExceptionCheck(JNIEnv *env)
returns true if an exception is pending.
Parameters:
env
the JNI interface pointer
void ExceptionClear(JNIEnv *env)
clears any pending exceptions.
Parameters:
env
the JNI interface pointer
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 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 used 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.optionString = "-Djava.compiler=NONE";
deactivates the just-in-time compiler.
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.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);
The C program in
Example 11-20
sets up a virtual machine and then calls the main method of the Welcome class, which was discussed in Chapter 2 of Volume 1. (Make sure to compile the Welcome.java file before starting the invocation test program.)
Example 11-20 InvocationTest.c 1. #include
2. #include
3.
4. int main()
5. {
6. JavaVMOption options[2];
7. JavaVMInitArgs vm_args;
8. JavaVM *jvm;
9. JNIEnv *env;
10. long status;
11.
12. jclass class_Welcome;
13. jclass class_String;
14. jobjectArray args;
15. jmethodID id_main;
16.
17. options[0].optionString = "-Djava.class.path=.";
18.
19. memset(&vm_args, 0, sizeof(vm_args));
20. vm_args.version = JNI_VERSION_1_2;
21. vm_args.nOptions = 1;
22. vm_args.options = options;
23.
24. status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
25. if (status == JNI_ERR)
26. {
27. printf("Error creating VM\n");
28. return 1;
29. }
30.
31. class_Welcome = (*env)->FindClass(env, "Welcome");
32. id_main = (*env)->GetStaticMethodID(env, class_Welcome,
33. "main", "([Ljava/lang/String;)V");
34.
35. class_String = (*env)->FindClass(env, "java/lang/String");
36. args = (*env)->NewObjectArray(env, 0, class_String, NULL);
37. (*env)->CallStaticVoidMethod(env, class_Welcome,
38. id_main, args);
39.
40. (*jvm)->DestroyJavaVM(jvm);
41.
42. return 0;
43. }
To compile this program under Linux, use:
gcc -I/usr/local/jdk/include -I/usr/local/jdk/include/linux
-o InvocationTest
-L/usr/local/jdk/jre/lib/i386/client
-ljvm
InvocationTest.c
Under Solaris, use:
cc -I/usr/local/jdk/include -I/usr/local/jdk/include/solaris
-o InvocationTest
-L/usr/local/jdk/jre/lib/sparc
-ljvm
InvocationTest.c
When compiling in Windows with the Microsoft C compiler, you use the command line:
cl -Ic:\jdk\include -Ic:\jdk\include\win32
InvocationTest.c c:\jdk\lib\jvm.lib
With Cygwin, you have to work a little harder. First make a file jvm.def that contains the statement:
EXPORTS
JNI_CreateJavaVM@12
Run the command:
dlltool -k --input-def jvm.def
--dll c:\\jdk\\jre\\bin\\hotspot\\jvm.dll
--output-lib jvm.a
Then compile with:
gcc -Ic:\jdk\include -Ic:\jdk\include\win32
-D__int64="long long"
-o InvocationTest
InvocationTest.c jvm.a
Before you run the program under Linux/UNIX, you must 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 command:
export LD_LIBRARY_PATH=/usr/local/jdk/jre/lib/i386/client
:/usr/local/jdk/jre/lib/i386
On Windows, make sure the directory
c:\jdk\jre\bin\hotspot
is on the PATH.
CAUTION
![]()
The exact locations of the various library files varies somewhat from one release of the SDK to the next. You may need to search for files called libjvm.so, jvm.dll, or (with older SDK versions) libjava.so, in the jdk/bin, jdk/lib, and jdk/jre/lib directories, and adjust the instructions accordingly.
TIP
![]()
If you develop an application that invokes the virtual machine by using a Windows launcher, then you may not trust your users to set the library path. You can help your users and load the shared library or DLL manually. The javac and java programs do just that. For sample code, see the file launcher/java_md.c in the src.jar file that is a part of the SDK.
Invocation API functions
![]()
jint JNI_CreateJavaVM(JavaVM** p_jvm, void** p_env, JavaVMInitArgs* vm_args)
initializes the Java virtual machine. The function returns 0 if successful, JNI_ERR on failure.
Parameters:
p_jvm
filled with a pointer to the invocation API function table
p_env
filled with a pointer to the JNI function table
vm_args
the virtual machine arguments
jint DestroyJavaVM(JavaVM* jvm)
destroys the virtual machine. Returns 0 on success, a negative number on failure. This function must be called through a virtual machine pointer, i.e., (*jvm)->DestroyJavaVM(jvm).
Parameters:
jvm
the virtual machine pointer
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. What we show you is 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.
An Overview of the Windows Registry
For those who are not familiar with the Windows registry: It is a data depository that is accessed by the Windows operating system, and that is available as a storage area for application programs. (A good book is Inside the Windows 95 Registry by Ron Petrusha [O'Reilly 1996].) In older versions of Windows, the operating system as well as applications used so-called INI files to store configuration parameters. Windows 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; placing them into the registry provides a single point for administration and backup.
On the downside, 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 for information on how to back up the Windows registry.
We don't suggest that you use the registry to store configuration parameters for your Java programs. XML files are a better solution—see
chapter 12
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 naïve but enthusiastic users, there is no icon for launching the registry editor. Instead, start a DOS shell (or open the Start->Run dialog) and type regedit.
Figure 11-3
shows the registry editor in action.
Figure 11-3. 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, the key
HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Development Kit\1.4
has two name/value pairs, such as,
JavaHome="c:\jdk1.4"
MicroVersion="0"
In this case, the values are strings. The values can also be integers or arrays of bytes.
A Java Platform Interface for Accessing the Registry
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 platform 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_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Development Kit\1.4
(You should change the key to the version of the SDK that is installed on your system.)
public static void main(String[] args)
{
Win32RegKey key = new Win32RegKey(
Win32RegKey.HKEY_LOCAL_MACHINE,
"SOFTWARE\JavaSoft\Java Development Kit\1.4");
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:
JavaHome=c:\jdk1.4
MicroVersion=0
Implementing the 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. 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 11-22
) 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, coping with the generic return type is not difficult. 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 11-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.
TIP
![]()
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:\jdk\include -Ic:\jdk\include\win32 -LD
Win32RegKey.c advapi32.lib -FeWin32RegKey.dll
With Cygwin, use
gcc -c -Ic:\jdk\include
-Ic:\jdk\include\win32 -Ic:\cygwin\usr\include\w32api
-D__int64="long long"
Win32RegKey.c
dllwrap --add-stdcall-alias -o Win32RegKey.dll Win32RegKey.o
Example 11-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_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Development Kit\1.4
You should edit the version number to match your SDK installation (or simply use some other existing registry key).
We then enumerate all names of that key and retrieve their values. The program should print out
JavaHome=c:\jdk1.4
MicroVersion=0
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 may want to use the registry editor to remove them after running this program.
Example 11-21 Win32RegKey.java 1. import java.util.*;
2.
3. /**
4. A Win32RegKey object can be used to get and set values of
5. a registry key in the Windows registry.
6. */
7. public class Win32RegKey
8. {
9. /**
10. Construct a registry key object.
11. @param theRoot one of HKEY_CLASSES_ROOT, HKEY_CURRENT_USER,
12. HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_CONFIG,
13. HKEY_DYN_DATA
14. @param thePath the registry key path
15. */
16. public Win32RegKey(int theRoot, String thePath)
17. {
18. root = theRoot;
19. path = thePath;
20. }
21.
22. /**
23. Enumerates all names of registry entries under the path
24. that this object describes.
25. @return an enumeration listing all entry names
26. */
27. public Enumeration 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. /**
40. Sets the value of a registry entry.
41. @param name the entry name
42. @param value the new value
43. */
44. public native void setValue(String name, Object value);
45.
46. public static final int HKEY_CLASSES_ROOT = 0x80000000;
47. public static final int HKEY_CURRENT_USER = 0x80000001;
48. public static final int HKEY_LOCAL_MACHINE = 0x80000002;
49. public static final int HKEY_USERS = 0x80000003;
50. public static final int HKEY_CURRENT_CONFIG = 0x80000005;
51. public static final int HKEY_DYN_DATA = 0x80000006;
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
63. {
64. Win32RegKeyNameEnumeration(int theRoot, String thePath)
65. {
66. root = theRoot;
67. path = thePath;
68. }
69.
70. public native Object nextElement();
71. public native boolean hasMoreElements();
72.
73. private int root;
74. private String path;
75. private int index = -1;
76. private int hkey = 0;
77. private int maxsize;
78. private int count;
79. }
80.
81. class Win32RegKeyException extends RuntimeException
82. {
83. public Win32RegKeyException() {}
84. public Win32RegKeyException(String why)
85. {
86. super(why);
87. }
88. }
Example 11-22 Win32RegKey.c 1. #include "Win32RegKey.h"
2. #include "Win32RegKeyNameEnumeration.h"
3. #include
4. #include
5. #include
6.
7. JNIEXPORT jobject JNICALL Java_Win32RegKey_getValue(JNIEnv* env,
8. jobject this_obj, jstring name)
9. {
10. const char* cname;
11. jstring path;
12. const char* cpath;
13. HKEY hkey;
14. DWORD type;
15. DWORD size;
16. jclass this_class;
17. jfieldID id_root;
18. jfieldID id_path;
19. HKEY root;
20. jobject ret;
21. char* cret;
22.
23. /* get the class */
24. this_class = (*env)->GetObjectClass(env, this_obj);
25.
26. /* get the field IDs */
27. id_root = (*env)->GetFieldID(env, this_class, "root", "I");
28. id_path = (*env)->GetFieldID(env, this_class, "path",
29. "Ljava/lang/String;");
30.
31. /* get the fields */
32. root = (HKEY)(*env)->GetIntField(env, this_obj, id_root);
33. path = (jstring)(*env)->GetObjectField(env, this_obj,
34. id_path);
35. cpath = (*env)->GetStringUTFChars(env, path, NULL);
36.
37. /* open the registry key */
38. if (RegOpenKeyEx(root, cpath, 0, KEY_READ, &hkey)
39. != ERROR_SUCCESS)
40. {
41. (*env)->ThrowNew(env,
42. (*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)
53. != ERROR_SUCCESS)
54. {
55. (*env)->ThrowNew(env,
56. (*env)->FindClass(env, "Win32RegKeyException"),
57. "Query value key failed");
58. RegCloseKey(hkey);
59. (*env)->ReleaseStringUTFChars(env, name, cname);
60. return NULL;
61. }
62.
63. /* get memory to hold the value */
64. cret = (char*)malloc(size);
65.
66. /* read the value */
67. if (RegQueryValueEx(hkey, cname, NULL, &type, cret, &size)
68. != ERROR_SUCCESS)
69. {
70. (*env)->ThrowNew(env,
71. (*env)->FindClass(env, "Win32RegKeyException"),
72. "Query value key failed");
73. free(cret);
74. RegCloseKey(hkey);
75. (*env)->ReleaseStringUTFChars(env, name, cname);
76. return NULL;
77. }
78.
79. /* depending on the type, store the value in a string,
80. integer or byte array */
81. if (type == REG_SZ)
82. {
83. ret = (*env)->NewStringUTF(env, cret);
84. }
85. else if (type == REG_DWORD)
86. {
87. jclass class_Integer = (*env)->FindClass(env,
88. "java/lang/Integer");
89. /* get the method ID of the constructor */
90. jmethodID id_Integer = (*env)->GetMethodID(env,
91. class_Integer, "", "(I)V");
92. int value = *(int*)cret;
93. /* invoke the constructor */
94. ret = (*env)->NewObject(env, class_Integer, id_Integer,
95. value);
96. }
97. else if (type == REG_BINARY)
98. {
99. ret = (*env)->NewByteArray(env, size);
100. (*env)->SetByteArrayRegion(env, (jarray)ret, 0, size,
101. cret);
102. }
103. else
104. {
105. (*env)->ThrowNew(env,
106. (*env)->FindClass(env, "Win32RegKeyException"),
107. "Unsupported value type");
108. ret = NULL;
109. }
110.
111. free(cret);
112. RegCloseKey(hkey);
113. (*env)->ReleaseStringUTFChars(env, name, cname);
114.
115. return ret;
116. }
117.
118. JNIEXPORT void JNICALL Java_Win32RegKey_setValue(JNIEnv* env,
119. jobject this_obj, jstring name, jobject value)
120. {
121. const char* cname;
122. jstring path;
123. const char* cpath;
124. HKEY hkey;
125. DWORD type;
126. DWORD size;
127. jclass this_class;
128. jclass class_value;
129. jclass class_Integer;
130. jfieldID id_root;
131. jfieldID id_path;
132. HKEY root;
133. const char* cvalue;
134. int ivalue;
135.
136. /* get the class */
137. this_class = (*env)->GetObjectClass(env, this_obj);
138.
139. /* get the field IDs */
140. id_root = (*env)->GetFieldID(env, this_class, "root", "I");
141. id_path = (*env)->GetFieldID(env, this_class, "path",
142. "Ljava/lang/String;");
143.
144. /* get the fields */
145. root = (HKEY)(*env)->GetIntField(env, this_obj, id_root);
146. path = (jstring)(*env)->GetObjectField(env, this_obj,
147. id_path);
148. cpath = (*env)->GetStringUTFChars(env, path, NULL);
149.
150. /* open the registry key */
151. if (RegOpenKeyEx(root, cpath, 0, KEY_WRITE, &hkey)
152. != ERROR_SUCCESS)
153. {
154. (*env)->ThrowNew(env,
155. (*env)->FindClass(env, "Win32RegKeyException"),
156. "Open key failed");
157. (*env)->ReleaseStringUTFChars(env, path, cpath);
158. return;
159. }
160.
161. (*env)->ReleaseStringUTFChars(env, path, cpath);
162. cname = (*env)->GetStringUTFChars(env, name, NULL);
163.
164. class_value = (*env)->GetObjectClass(env, value);
165. class_Integer = (*env)->FindClass(env, "java/lang/Integer");
166. /* determine the type of the value object */
167. if ((*env)->IsAssignableFrom(env, class_value,
168. (*env)->FindClass(env, "java/lang/String")))
169. {
170. /* it is a string--get a pointer to the characters */
171. cvalue = (*env)->GetStringUTFChars(env, (jstring)value,
172. NULL);
173. type = REG_SZ;
174. size = (*env)->GetStringLength(env, (jstring)value) + 1;
175. }
176. else if ((*env)->IsAssignableFrom(env, class_value,
177. class_Integer))
178. {
179. /* it is an integer--call intValue to get the value */
180. jmethodID id_intValue = (*env)->GetMethodID(env,
181. class_Integer, "intValue", "()I");
182. ivalue = (*env)->CallIntMethod(env, value, id_intValue);
183. type = REG_DWORD;
184. cvalue = (char*)&ivalue;
185. size = 4;
186. }
187. else if ((*env)->IsAssignableFrom(env, class_value,
188. (*env)->FindClass(env, "[B")))
189. {
190. /* it is a byte array--get a pointer to the bytes */
191. type = REG_BINARY;
192. cvalue = (char*)(*env)->GetByteArrayElements(env,
193. (jarray)value, NULL);
194. size = (*env)->GetArrayLength(env, (jarray)value);
195. }
196. else
197. {
198. /* we don't know how to handle this type */
199. (*env)->ThrowNew(env,
200. (*env)->FindClass(env, "Win32RegKeyException"),
201. "Unsupported value type");
202. RegCloseKey(hkey);
203. (*env)->ReleaseStringUTFChars(env, name, cname);
204. return;
205. }
206.
207. /* set the value */
208. if (RegSetValueEx(hkey, cname, 0, type, cvalue, size)
209. != ERROR_SUCCESS)
210. { (*env)->ThrowNew(env,
211. (*env)->FindClass(env, "Win32RegKeyException"),
212. "Query value key failed");
213. }
214.
215. RegCloseKey(hkey);
216. (*env)->ReleaseStringUTFChars(env, name, cname);
217.
218. /* if the value was a string or byte array, release the
219. pointer */
220. if (type == REG_SZ)
221. {
222. (*env)->ReleaseStringUTFChars(env, (jstring)value,
223. cvalue);
224. }
225. else if (type == REG_BINARY)
226. {
227. (*env)->ReleaseByteArrayElements(env, (jarray)value,
228. cvalue, 0);
229. }
230. }
231.
232. /* helper function to start enumeration of names */
233. static int startNameEnumeration(JNIEnv* env, jobject this_obj,
234. jclass this_class)
235. {
236. jfieldID id_index;
237. jfieldID id_count;
238. jfieldID id_root;
239. jfieldID id_path;
240. jfieldID id_hkey;
241. jfieldID id_maxsize;
242.
243. HKEY root;
244. jstring path;
245. const char* cpath;
246. HKEY hkey;
247. int maxsize = 0;
248. int count = 0;
249.
250. /* get the field IDs */
251. id_root = (*env)->GetFieldID(env, this_class, "root", "I");
252. id_path = (*env)->GetFieldID(env, this_class, "path",
253. "Ljava/lang/String;");
254. id_hkey = (*env)->GetFieldID(env, this_class, "hkey", "I");
255. id_maxsize = (*env)->GetFieldID(env, this_class, "maxsize",
256. "I");
257. id_index = (*env)->GetFieldID(env, this_class, "index",
258. "I");
259. id_count = (*env)->GetFieldID(env, this_class, "count",
260. "I");
261.
262. /* get the field values */
263. root = (HKEY)(*env)->GetIntField(env, this_obj, id_root);
264. path = (jstring)(*env)->GetObjectField(env, this_obj,
265. id_path);
266. cpath = (*env)->GetStringUTFChars(env, path, NULL);
267.
268. /* open the registry key */
269. if (RegOpenKeyEx(root, cpath, 0, KEY_READ, &hkey)
270. != ERROR_SUCCESS)
271. {
272. (*env)->ThrowNew(env,
273. (*env)->FindClass(env, "Win32RegKeyException"),
274. "Open key failed");
275. (*env)->ReleaseStringUTFChars(env, path, cpath);
276. return -1;
277. }
278. (*env)->ReleaseStringUTFChars(env, path, cpath);
279.
280. /* query count and max length of names */
281. if (RegQueryInfoKey(hkey, NULL, NULL, NULL, NULL,
282. NULL, NULL, &count, &maxsize, NULL, NULL, NULL)
283. != ERROR_SUCCESS)
284. {
285. (*env)->ThrowNew(env,
286. (*env)->FindClass(env, "Win32RegKeyException"),
287. "Query info key failed");
288. return -1;
289. }
290.
291. /* set the field values */
292. (*env)->SetIntField(env, this_obj, id_hkey, (DWORD)hkey);
293. (*env)->SetIntField(env, this_obj, id_maxsize, maxsize + 1);
294. (*env)->SetIntField(env, this_obj, id_index, 0);
295. (*env)->SetIntField(env, this_obj, id_count, count);
296. return count;
297. }
298.
299. JNIEXPORT jboolean JNICALL
300. Java_Win32RegKeyNameEnumeration_hasMoreElements(JNIEnv* env,
301. jobject this_obj)
302. { jclass this_class;
303. jfieldID id_index;
304. jfieldID id_count;
305. int index;
306. int count;
307. /* get the class */
308. this_class = (*env)->GetObjectClass(env, this_obj);
309.
310. /* get the field IDs */
311. id_index = (*env)->GetFieldID(env, this_class, "index",
312. "I");
313. id_count = (*env)->GetFieldID(env, this_class, "count",
314. "I");
315.
316. index = (*env)->GetIntField(env, this_obj, id_index);
317. if (index == -1) /* first time */
318. {
319. count = startNameEnumeration(env, this_obj, this_class);
320. index = 0;
321. }
322. else
323. count = (*env)->GetIntField(env, this_obj, id_count);
324. return index GetObjectClass(env, this_obj);
347.
348. /* get the field IDs */
349. id_index = (*env)->GetFieldID(env, this_class, "index",
350. "I");
351. id_count = (*env)->GetFieldID(env, this_class, "count",
352. "I");
353. id_hkey = (*env)->GetFieldID(env, this_class, "hkey", "I");
354. id_maxsize = (*env)->GetFieldID(env, this_class, "maxsize",
355. "I");
356.
357. index = (*env)->GetIntField(env, this_obj, id_index);
358. if (index == -1) /* first time */
359. {
360. count = startNameEnumeration(env, this_obj, this_class);
361. index = 0;
362. }
363. else
364. count = (*env)->GetIntField(env, this_obj, id_count);
365.
366. if (index >= count) /* already at end */
367. {
368. (*env)->ThrowNew(env,
369. (*env)->FindClass(env,
370. "java/util/NoSuchElementException"),
371. "past end of enumeration");
372. return NULL;
373. }
374.
375. maxsize = (*env)->GetIntField(env, this_obj, id_maxsize);
376. hkey = (HKEY)(*env)->GetIntField(env, this_obj, id_hkey);
377. cret = (char*)malloc(maxsize);
378.
379. /* find the next name */
380. if (RegEnumValue(hkey, index, cret, &maxsize, NULL, NULL,
381. NULL, NULL) != ERROR_SUCCESS)
382. {
383. (*env)->ThrowNew(env,
384. (*env)->FindClass(env, "Win32RegKeyException"),
385. "Enum value failed");
386. free(cret);
387. RegCloseKey(hkey);
388. (*env)->SetIntField(env, this_obj, id_index, count);
389. return NULL;
390. }
391.
392. ret = (*env)->NewStringUTF(env, cret);
393. free(cret);
394.
395. /* increment index */
396. index++;
397. (*env)->SetIntField(env, this_obj, id_index, index);
398.
399. if (index == count) /* at end */
400. {
401. RegCloseKey(hkey);
402. }
403.
404. return ret;
405. }
Example 11-23 Win32RegKeyTest.java 1. import java.util.*;
2.
3. public class Win32RegKeyTest
4. {
5. public static void main(String[] args)
6. {
7. Win32RegKey key = new Win32RegKey(
8. Win32RegKey.HKEY_LOCAL_MACHINE,
9. "SOFTWARE\\JavaSoft\\Java Development Kit\\1.4");
10.
11. key.setValue("Default user", "Harry Hacker");
12. key.setValue("Lucky number", new Integer(13));
13. key.setValue("Small primes", new byte[]
14. { 2, 3, 5, 7, 11 });
15.
16. Enumeration enum = key.names();
17.
18. while (enum.hasMoreElements())
19. {
20. String name = (String)enum.nextElement();
21. System.out.print(name + "=");
22.
23. Object value = key.getValue(name);
24.
25. if (value instanceof byte[])
26. {
27. byte[] bvalue = (byte[])value;
28. for (int i = 0; i
Type inquiry functions
![]()
jboolean IsAssignableFrom(JNIEnv *env, jclass cl1, jclass cl2)
returns JNI_TRUE if objects of the first class can be assigned to objects of the second class; JNI_FALSE otherwise. This is the case when the classes are the same, cl1 is a subclass of cl2, or cl2 represents an interface that is implemented by cl1 or one of its superclasses.
Parameters:
env
the JNI interface pointer
cl1, cl2
class references
jclass GetSuperClass(JNIEnv *env, jclass cl)
returns the superclass of a class. If cl represents the class Object or an interface, returns NULL.
Parameters:
env
the JNI interface pointer
cl
a class reference
本文來自ChinaUnix博客,如果查看原文請點(diǎn):http://blog.chinaunix.net/u/22516/showart_161709.html |
|