Chapter 10. Native Methods

We hope that you are convinced that Java code has a number of advantages over code written in languages like C or C++—even for platform-specific applications. Here, of course, it is not portability that is the issue but rather features like these:

  • You are more likely to produce bug-free code using Java than C or C++.

  • Multithreading is probably easier to code in Java than in any other language.

  • Networking code is a breeze.

Portability is simply a bonus that you may or may not want to take advantage of down the line.

While a “100% Pure Java” solution is nice in principle, realistically, for an application, there are situations where you will want to write (or use) code written in another language. (Such code is usually called native code.) There are three obvious reasons why this may be the right choice:

  1. You have substantial amounts of tested and debugged code available in that language. Porting the code to Java would be time consuming, and the resulting code would need to be tested and debugged again.

  2. Your application requires access to system features or devices, and using Java would be cumbersome, at best, or impossible, at worst.

  3. Maximizing the speed of the code is essential. For example, the task may be time critical or it may be code that is used so often that optimizing it has a big payoff. (This is actually the least plausible reason. With just-in-time compilation (JIT), intensive computations coded in Java are not that much slower than compiled C code.)

If you are in one of these three situations, it might make sense to call the code from Java. Of course, with the usual security manager in place, once you start using native code, you are restricted to applications rather than applets. In particular, the native code library you are calling must exist on the client machine, and it must work with the client machine architecture.

To make calling native methods possible, Java comes with hooks for working with system libraries, and the JDK has a few tools to relieve some (but not all) of the tedium necessary.

NOTE

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 Java-to-FORTRAN binding, or Visual Basic, if you have access to a Java binding for its types.

Still, keep in mind:

Use native methods and you lose portability.

Even when you distribute your program as an application, you must supply a separate native method library for every platform you wish to support. This means you must also educate your users on how to install these libraries! Also, while users may trust that applets can neither damage data nor steal confidential information, they may not want to extend the same trust to Java code that uses native method libraries. For that reason, many potential users will be reluctant to use Java programs that require native code. Aside from the security issue, native libraries are unlikely to be as safe as Java code, especially if they are written in a language like C or C++ that offers no protection against over-writing memory through invalid pointer usage. It is easy to write native methods that corrupt the Java virtual machine, compromise its security, or trash the operating system.

Thus, we suggest using native code only as a last resort. If you must gain access to a device such as the serial port in a Java program, then you may need to write native code. If you need to access an existing body of code, why not consider native methods as a stopgap measure and eventually port the code to Java? If you are concerned about efficiency, benchmark a Java implementation. In most cases, the speed of Java using a JIT will be sufficient. (Just-in-time compilers and other emerging compiler technologies will soon exist on all platforms that Java has been ported to. They already exist under Windows and Solaris.) A talk at the 1996 JavaOne conference showed this clearly. The implementers of the cryptography library at Sun Microsystems reported that a pure Java implementation of their cryptographic functions was more than adequate. It was true that they were not as fast as a C implementation would have been, but it turned out not to matter. The Java implementation was far faster than the network I/O. And this turns out to be the real bottleneck.

In summary, there is no point in sacrificing portability for a meaningless speed improvement; don’t go native until you determine that you have no other choice.

NOTE

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 is still being used by the Microsoft Virtual Machine. JavaSoft has assured developers that the JNI binding described here is a permanent part of Java, and that it needs to be supported by all Java Virtual Machines.

Finally, we use C as our language for native methods in this chapter because C is probably the language most often used for Java native methods. In particular, you’ll see how to make the correspondence between Java and C data types, feature names, and function calls. (This correspondence is usually called the C binding.)

C++ NOTE

C++ NOTE

You can also use C++ instead of C to write native methods. There are a few advantages—type checking is slightly stricter, and accessing the JNI functions is a bit more convenient. However, JNI does not support any direct correspondence between classes in Java and C++. Even when programming with C++, you must access the fields and methods of Java classes through C function calls.

Calling a C Function from Java

Suppose you have a C function that does something you like and, for one reason or another, you don’t want to bother reimplementing it in Java. For the sake of illustration, we’ll assume it is the useful and venerable printf function. You want to be able to call printf from your Java programs. Java sensibly uses the keyword native for a native method, and you will obviously need to encapsulate the printf function in a class. So, you might write something like:

public class Printf 
{  public native String printf(String s); 
}

You actually can compile this class but when you go to use it in another program, and call the Java interpreter, then the interpreter will tell you it doesn’t know how to find printf —reporting an UnsatisfiedLinkError. So the trick is to give the run time enough information so that it can link in this class. As you will soon see, under the JDK this requires a three-step process:

  1. Generate a C stub for a function that translates between the Java call and the actual C function. The stub does this translation by taking information off the Java stack and passing it to the compiled C function.

  2. Create a special shared library and export the stub from it.

  3. Use a special Java method, called System.LoadLibrary. to tell the Java run time to load the library from Step 2.

We now show you how to carry out these steps for various kinds of examples, starting from a trivial special-case use of printf and ending with a realistic example involving the registry function for Windows—examples that are obviously not available directly from Java.

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 Java compiler that the method will be defined externally. Of course, native methods will contain no Java code, and the method header is followed immediately by a terminating semicolon. This means, as you saw in the example above, native method declarations look similar to abstract method declarations.

class HelloNative 
{  public native static void greeting(); 
   . . . 

}

In this particular example, note that we also declare the native method as static. Native methods can be both static and nonstatic. Note that this method takes no parameters; we do not yet want to deal with parameter passing, not even implicit parameters.

Next, write a corresponding C function. You must name that function exactly the way the Java run-time system expects. Here are the rules:

  1. Use the full Java method name, such as HelloNative.greeting. If the class is in a package, then prepend the package name, such as corejava.Day.greeting.

  2. , Replace every period with an underscore, and prepend the prefix Java_. For example, Java_ HelloNative_greeting or Java_corejava_Day_greeting.

  3. If the class name contains characters that are not ASCII letters or digits—that is, '_', '$', or Unicode characters with code > 'U007F' —replace them with _0 xxxx , where xxxx is the sequence of four hexadecimal digits of the character’s Unicode value.

NOTE

NOTE

If you overload native methods, that is, if you provide multiple native methods with the same name, then you must append a double underscore followed by the encoded argument types. We describe the encoding of the argument types later in this chapter. For example, if you have a native method, greeting, and another native method greeting(int repeat), then the first one is called Java_HelloNative_greeting__, and the second, Java_HelloNative_greeting__I.

Actually, nobody does this by hand; instead, you should run the javah utility, which automatically generates the function names. To use javah first, compile the Java file (given in Example 10-3):

javac HelloNative.java

Next, call the javah utility to produce a C header file. The javah executable can be found in the jdkin directory.

javah -jni HelloNative

Using javah creates a header file, HelloNative.h, as in Example 10-1:

Example 10-1. HelloNative.h

/* DO NOT EDIT THIS FILE - it is machine generated */ 
#include <jni.h> 
/* Header for class HelloNative */ 

#ifndef _Included_HelloNative 
#define _Included_HelloNative 
#ifdef __cplusplus 
extern "C" {
#endif 
/* 
 * Class:     HelloNative 
 * Method:    greeting 
 * Signature: ()V 
 */ 
JNIEXPORT void JNICALL Java_HelloNative_greeting 
  (JNIEnv *, jclass); 
#ifdef __cplusplus 
} 
#endif 
#endif

As you can see, this file contains the declaration of a function Java_HelloNative_greeting. (The strings JNIEXPORT and JNICALL are defined in the header file jni.h. They denote compiler-dependent specifiers for exported functions that come from a dynamically loaded library.)

NOTE

NOTE

Be sure to use the -jni flag when running javah. Without that flag, you get the header file for the Java 1.0 binding.

Now, you simply have to copy the function prototype from the header file into the source file and to give the implementation code for the function, as shown in Example 10-2.

Example 10-2. HelloNative.c

#include "HelloNative.h" 
#include <stdio.h> 

JNIEXPORT void JNICALL Java_HelloNative_greeting 
   (JNIEnv* env, jclass cl) 
{  printf("Hello, Native World!
"); 
}

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

C++ NOTE

C++ NOTE

You can use C++ to implement native methods. However, you must then declare the functions that are called from Java as extern "C". For example,

#include "HelloNative.h" 
#include <stdio.h> 

extern "C" 
JNIEXPORT void JNICALL Java_HelloNative_greeting 
   (JNIEnv* env, jclass cl) 
{  printf("Hello, Native World!
"); 
}

Next, compile the C code into a dynamically loaded library. For example, with the Microsoft C++ compiler under Windows, the command you use is

cl -Ic:jdkinclude -Ic:jdkincludewin32 -LD HelloNative.c 
   -FeHelloNative.dll

With the Sun compiler under Solaris, the command to use is

cc -G -I/usr/local/java/include -
   I/usr/local/java/include/solaris 
      HelloNative.c -o libHelloNative.so

(You may need to use different paths to specify the locations of the header files, depending on which directory contains the JDK.)

TIP

If you use the Microsoft C++ compiler to compile DLLs from a DOS shell, first run the batch file

c:devstudiovcinvcvars32.bat

That batch file properly configures the command-line compiler by setting up the path and the environment variables needed by the compiler.

Finally, we need to add the call to the System.loadLibrary method that ensures that Java will load the library prior to the first use of the class. The easiest way to do this is to use a static initialization block in the class that contains the native method, as in Example 10-3:

Example 10-3. HelloNative.java

class HelloNative 
{  public native static void greeting(); 
   static 
   {System.loadLibrary("HelloNative"); 
   } 
}

Assuming you have followed all the steps given above, you are now ready to run the HelloNativeTest application shown in Example 10-4.

Example 10-4. HelloNativeTest.java

class HelloNativeTest 
{  public static void main(String[] args) 
   {  HelloNative.greeting(); 
   } 
}

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

Of course, this is not particularly impressive by itself. However, if you keep in mind that this message is generated by the C printf command and not by any Java code, you will see that we have taken the first steps toward bridging the gap between Java and C!

Numeric Parameters and Return Values

When passing numbers between C and Java, you need to understand which C types correspond to which Java types. For example, while C does have data types called int and long, their implementation is platform dependent. On some platforms, ints are 16-bit quantities, and on others they are 32-bit quantities. In Java, of course, an int is always a 32-bit integer. For that reason, the Java Native Interface defines types jint, jlong, and so on.

Table 10-1 shows the correspondence between Java and C types.

Table 10-1. Java and C types

Java

C

bytes

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 Java has no elegant way for formatted printing of floating-point numbers. (Of course, you can use the DecimalFormat class (see Volume 1, Chapter 3) and build custom formats—we just don’t think this is as easy as a simple call to printf.) Since printf is quick, easy, and well known, let’s suppose you decide to implement the same functionality via a call to the printf function in a native method.

We don’t actually recommend this approach the native code needs to be compiled for every target platform. We are using it because it shows off the techniques of passing parameters to a native method and obtaining a return value.

Example 10-5 shows a class called Printf1 that uses a native method to print a floating-point number with a given field width and precision.

Example 10-5. Printf1.java

class Printf1 
{  public static native int print(int width, int 
      precision, double x); 
   static 
   {  System.loadLibrary("Printf1"); 
   } 
}

Notice that when implementing the method in C, we must change all int and double parameters to jint and jdouble, as shown in Example 10-6.

Example 10-6. Printf1.c

#include "Printf1.h" 
#include <stdio.h> 

JNIEXPORT jint JNICALL Java_Printf1_print 
  (JNIEnv* env, jclass cl, jint width, jint precision, jdouble x) 
{  char fmt[30]; 
   jint ret; 
   sprintf(fmt, "%%%d.%df", width, precision); 
   ret = printf(fmt, x); 
   return ret; 
}

The function simply assembles a format string "% w.p f" in the variable fmt, then calls printf. It then returns the number of characters it printed.

Example 10-7 shows the test program that demonstrates the Printf1 class.

Example 10-7. Printf1Test.java

class Printf1Test 
{  public static void main(String[] args) 
   {  int count = Printf1.print(8, 4, 3.14); 
      count += Printf1.print(8, 4, (double)count); 
      System.out.println(); 
      for (int i = 0; i < count; i++) 
         System.out.print("-"); 
      System.out.println(); 
   } 
}

String Parameters

Next, we want to consider how to transfer strings to and from native methods. As you know, Java strings are sequences of 16-bit Unicode characters; C strings are null-terminated strings of 8-bit characters, so strings in Java and C are quite different. The Java Native Interface has two sets of functions, one that converts Java strings to UTF (Unicode Text Format) and one that converts them to arrays of Unicode characters, that is, to jchar arrays. The UTF format was discussed in Chapter 1—recall that ASCII characters are encoded “as is,” but all other Unicode characters are encoded as 2-byte or 3-byte sequences.

If your C code already uses Unicode, you’ll want to use the second set of conversion functions. On the other hand, if all your Java strings are restricted to ASCII characters, you can use the UTF conversion functions.

A native method with a String parameter in its Java declaration actually receives a value of an opaque type called jstring. A native method with a Java return value of type String must return a value of type jstring. JNI functions are used to read and construct these jstring objects. For example, the NewStringUTF function makes a new jstring object out of a char array that contains UTF-encoded characters. Unfortunately, JNI functions have a somewhat odd calling convention. Here is a call to the NewStringUTF function:

JNIEXPORT jstring JNICALL Java_HelloNative_getGreeting 
   (JNIEnv* env, jclass cl) 
{  jstring jstr; 
   char greeting[] = "Hello, Native World
"; 
   jstr = (*env)->NewStringUTF(env, greeting); 
   return jstr; 
}

All calls to JNI functions use the env pointer that is the first argument of every native method. The env pointer is a pointer to a table of function pointers (see Figure 10-1). Therefore, you must prefix every JNI call with (*env)-> in order to actually dereference the function pointer. Furthermore, you must supply env as the first parameter of every JNI function. This setup is somewhat cumbersome, and it could have easily been made more transparent to C programmers. We suggest that you simply supply the (*env)-> prefix without worrying about the table of function pointers.

The env pointer

Figure 10-1. The env pointer

C++ NOTE

C++ NOTE

It is simpler to access JNI functions in C++. The C++ version of the JNIEnv class has inline member functions that take care of the function pointer lookup for you. For example, you can call the NewStringUTF function as

jstr = env->NewStringUTF(greeting);

Note that you omit the JNIEnv pointer from the paramenter list of the call.

The NewStringUTF function lets you construct a new jstring. To read the contents of an existing jstring object, use the GetStringUTFChars function. This function returns a const jbyte* pointer to the UTF characters that describe the character string. Note that a specific virtual machine is free to use UTF for its internal string representation, so you may get a character pointer into the actual Java string. Since Java strings are meant to be immutable, it is very important that you treat the const seriously and do not try to write into this character array. On the other hand, if the virtual machine uses Unicode characters for its internal string representation, then this function call allocates a new memory block that will be filled with the UTF equivalents.

The virtual machine must know when you are finished using the UTF string, so that it can garbage-collect it. (The garbage collector runs in a separate thread, and it can interrupt the execution of native methods.) For that reason, you must call the ReleaseStringUTFChars function.

Finally, the GetStringUTFLength function returns the number of characters needed for the UTF encoding of the string.

A Java Equivalent of sprintf

Let us put these functions we just described to work and write a Java equivalent of the C function sprintf. We would like to call the function as shown in Example 10-8.

Example 10-8. Printf2Test.java

class Printf2Test 
{  public static void main(String[] args) 
   {  double price = 44.95; 
      double tax = 7.75; 
      double amountDue = price * (1 + tax / 100); 

      String s = Printf2.sprint("Amount due = %8.2f", 
         amountDue); 
      System.out.println(s); 
   } 
}

Example 10-9 shows the class with the native sprint method.

Example 10-9. Printf2.java

class Printf2 
{  public static native String sprint(String format, double x); 
   static 
   {  System.loadLibrary("Printf2"); 
   } 
}

Therefore, the C function that formats a floating-point number has the prototype

JNIEXPORT jstring JNICALL Java_Printf2_sprint 
   (JNIEnv* env, jclass cl, jstring format, jdouble x)

Example 10-10 shows the code for the C implementation. Note the calls to GetStringUTFChars to read the format argument, NewStringUTF to generate the return value, and ReleaseStringUTFChars to inform the virtual machine that access to the string is no longer required.

Example 10-10. Printf2.c

#include "Printf2.h" 
#include <string.h> 
#include <stdlib.h> 
#include <float.h> 

char* find_format(const char format[]) 
/** 
 * @param format a string containing a printf format specifier 
 * (such as "%8.2f"). Substrings "%%" are skipped. 
 * @return a pointer to the format specifier (skipping the '%') 
 * or NULL if there wasn't a unique format specifier 
 */ 
{  char* p; 
   char* q; 
   size_t n; 

   p = strchr(format, '%'), 
   while (p != NULL && *(p + 1) == '%') /* skip %% */ 
      p = strchr(p + 2, '%'), 
   if (p == NULL) return NULL; 
   /* now check that % is unique */ 
   p++; 
   q = strchr(p, '%'), 
   while (q != NULL && *(q + 1) == '%') /* skip %% */ 
      q = strchr(q + 2, '%'), 
   if (q != NULL) return NULL; /* % not unique */ 
   q = p + strspn(p, " -0+#"); /* skip past flags */ 
   q += strspn(q, "0123456789"); /* skip past field width */ 
   if (*q == '.') { q++; q += strspn(q, "0123456789"); } 
      /* skip past precision */ 
   if (strchr("eEfFgG", *q) == NULL) return NULL; 
      /* not a floating point format */ 
   return p; 
} 

JNIEXPORT jstring JNICALL Java_Printf2_sprint 
   (JNIEnv* env, jclass cl, jstring format, jdouble x) 
{  const char* cformat; 
   char* fmt; 
   jstring ret; 

   cformat = (*env)->GetStringUTFChars(env, format, NULL); 
   fmt = find_format(cformat); 
   if (fmt == NULL) 
      ret = format; 
   else 
   {  char* cret; 
      int width = atoi(fmt); 
      if (width == 0) width = DBL_DIG + 10; 
      cret = (char*)malloc(strlen(cformat) + width); 
      sprintf(cret, cformat, x); 
      ret = (*env)->NewStringUTF(env, cret); 
      free(cret); 
   } 
   (*env)->ReleaseStringUTFChars(env, format, cformat); 
   return ret; 
}

In this function, we chose to keep the error handling simple. If the format code to print a floating-point number is not of the form %w.pc , where c is one of the characters e, E, f, g, or G, then what we simply do is not format the number. You will see later how to make a native method throw an exception.

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 methods of the Employee class, using native methods. Again, this is not something you would normally want to do, but it does illustrate how to access object fields from a native method when you need to do so. First, consider the raiseSalary method. In Java, the code was simple.

public void raiseSalary(double byPercent) 
{  salary *= 1 + byPercent / 100; 
}

Let us rewrite this as a native method. Unlike the previous examples of native methods, this is not a static method. Running javah gives the following prototype.

JNIEXPORT void JNICALL Java_Employee_raiseSalary 
   (JNIEnv *, jobject, jdouble);

Note the second argument. It is no longer of type jclass but of type jobject. In fact, it is the equivalent of the this reference. Static methods obtain a reference to the class, whereas nonstatic methods obtain a reference to the implicit this argument object.

Now we access the salary field of the implicit argument. In the “raw” Java-to-C binding of Java 1.0, this was easy—a programmer could directly access object data fields. However, direct access requires all virtual machines to expose their internal data layout. For that reason, the JNI requires programmers to get and set the values of data fields by calling special JNI functions.

In our case, we need to use the GetDoubleField and SetDoubleField functions because the type of salary is a double. There are other functions— GetIntField/SetIntField, GetObjectField/SetObjectField, and so on—for other field types. The general syntax is:

x = (*env)->GetXxxField(env, class, fieldID); 
(*env)->GetXxxField(env, class, fieldID, x);

Here, class is a value that represents a Java object of type Class, and fieldID is a value of a special type, jfieldID, that denotes the offset of a field in a structure and Xxx represents a Java data type (Object, Boolean, byte, and so on). There are two ways for obtaining the class object. The GetObjectClass func-tion returns the class of any object. For example:

jclass class_Employee = (*env)->GetObjectClass(env, obj_this);

The FindClass function lets you specify the class name as a string (curiously, with / instead of periods as package name separators).

jclass class_Day = (*env)->FindClass(env, "corejava/Day");

Use the GetFieldID function to obtain the fieldID. You must supply the name of the field and its signature, an encoding of its type. For example, here is the code to obtain the field ID of the salary field.

jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, 
   "salary", "D");

The string "D" denotes the type double. You will learn the complete rules for encoding signatures in the next section.

You may be thinking that accessing a data field seems quite convoluted. However, since the designers of the JNI did not want to expose the data fields directly, they had to supply functions for getting and setting field values. To minimize the cost of these functions, computing the field ID from the field name—which is the most expensive step—is factored out into a separate step. That is, if you repeatedly get and set the value of a particular field, you incur only once the cost of computing the field offset. Let us put all the pieces together. The following code reimplements the raiseSalary method as a native method.

JNIEXPORT void JNICALL Java_Employee_raiseSalary 
  (JNIEnv* env, jobject obj_this, jdouble byPercent) 
{  /* get the class */ 
   jclass class_Employee = (*env)->GetObjectClass(env, 
      obj_this); 

   /* get the field ID */ 
   jfieldID id_salary = (*env)->GetFieldID(env, class_Employee, 
      "salary", "D"); 

   /* get the field value */ 
   jdouble salary = (*env)->GetDoubleField(env, obj_this, 
      id_salary); 

   salary *= 1 + byPercent / 100; 

   /* set the field value */ 
   (*env)->SetDoubleField(env, obj_this, id_salary, salary); 
}

Accessing Static Fields

Accessing static fields is similar to accessing nonstatic fields. You use the GetStaticFieldID and GetStatic Xxx Field/SetStatic Xxx Field functions. They work almost identically to their nonstatic counterpart. There are only two differences.

  • Since you have no object, you must use FindClass instead of GetObjectClass to obtain the class reference.

  • You supply the class, not the instance object, when accessing the field.

For example, here is how you can get a reference to System.out :

/* get the class */ 
jclass class_System = (*env)->FindClass(env, 
   "java/lang/System"); 

/* get the field ID */ 
jfieldID id_out = (*env)->GetStaticFieldID(env, 
   class_System, "out", "Ljava/io/PrintStream;"); 

/* get the field value */ 
jobject obj_out = (*env)->GetStaticObjectField(env, 
   class_System, id_out);

Signatures

To access object fields and call Java methods, learn the rules for “mangling” the names of data types and method signatures. (A method signature describes the parameters and return type of the method.) Here is the encoding scheme:

B

byte

C

char

D

double

F

float

I

int

J

long

L classname;

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, corejava.Day)

has a signature

"(Ljava/lang/String;DLcorejava/Day;)V"

As you can see, there is no separator between the D and Lcorejava/Day ;.

Also note that in this encoding scheme, you must use / instead of . to separate the package and class names.

To describe an array type, use a [. For example, an array of strings is

[Ljava/lang/String;

A float[][] is mangled into

[[F

For the complete signature of a method, you list the parameter types inside a pair of parentheses and then list the return type. For example, a method receiving two integers and returning an integer is encoded as

(II)I

The print method that we used in the preceding example has a mangled signature of

(Ljava/lang/String;)V

That is, the method receives a string and returns void.

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 corejava.Day hireDay; 
          /*   Lcorejava/Day;   */ 
       public Employee(java.lang.String,double,corejava.Day); 
          /*   (Ljava/lang/String;DLcorejava/Day;)V   */ 
       public void print(); 
          /*   ()V   */ 
       public void raiseSalary(double); 
          /*   (D)V   */ 
       public int hireYear(); 
          /*   ()I   */ 
  }

NOTE

NOTE

There is no rational reason why programmers are forced to use this mangling scheme for describing signatures. The designers of Java could have just as easily written a function that reads Java-style signatures, such as void(int,java.lang.String), and encodes them into whatever internal representation they prefer. Then again, using the mangled signatures lets you partake in the mystique of programming close to the virtual machine.

Calling Java Methods

Of course, Java functions can call C functions—that is, what native methods are for. Can we go the other way and call Java code from C? Why would we want to do this anyway? The answer is that it often happens that a native method needs to request a service from an object that was passed to it. We first show you how to do it for nonstatic methods,and then we show you how to do it for static methods.

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 ps.print(s) from the C function that implements the native method.

You can call any Java method from C by using the function call

(*env)->CallXxxMethod(env, implicit parameter, methodID, 
   explicit parameters)

Replace Xxx with Void, Int, Object, etc., depending on the return type of the method. Just as you need a fieldID to access a field of an object, you need a method ID to call a method. You obtain a method ID by calling the JNI function GetMethodID and supplying the class, the name of the method, and the method signature.

In our example, we want to obtain the ID of the print method of the PrintWriter class. As you saw in Chapter 1, the PrintWriter class has nine different methods, all called print. For that reason, you must also supply a string describing the parameters and return value of the specific function that you want to use. For example, we want to use void print(java.lang.String). As described in the preceding section, we must now “mangle” the signature into the string "(Ljava/lang/string;)V".

Here is the complete code to make the method call, by:

  1. Obtaining the class of the implicit parameter

  2. Obtaining the method ID

  3. Making the call

    /* get the class */ 
    class_PrintWriter = (*env)->GetObjectClass(env, out); 
    
    /* get the method ID */ 
    id_print = (*env)->GetMethodID(env, class_PrintWriter, 
       "print", "(Ljava/lang/String;)V"); 
    
    /* call the method */ 
    (*env)->CallVoidMethod(env, out, id_print, str);

Static Methods

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

  • You use the GetStaticMethodID and CallStatic Xxx Method functions.

  • You supply a class object, not an implicit parameter object, when invoking the method.

As an example of this, let’s make the call to the static method

System.getProperty("java.class.path")

from a native method. The return value of this call is a string that gives the current class path.

First, we need to find the class to use. Since we have no object of the class System readily available, we use FindClass rather than GetObjectClass.

jclass class_System = (*env)->FindClass(env, 
"java/lang/System");

Next, we need the ID of the static getProperty method. The encoded signature of that method is

"(Ljava/lang/String;)Ljava/lang/String;"

since both the parameter and the return value are a string. Hence, we obtain the method ID as follows:

jmethodID id_getProperty = (*env)->GetStaticMethodID(env, 
   class_System, 
   "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");

Finally, we can make the call. Note that the class object is passed to the CallStaticStringMethod function.

jobject obj_ret = (*env)->CallStaticObjectMethod(env, 
   class_System, 
   (*env)->NewStringUTF(env, "java.class.path"));

The return value of this method is of type jobject. If we want to manipulate it as a string, we must cast it to jstring :

jstring str_ret = (jstring)obj_ret;

C++ NOTE

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 "<init>" and the encoded signature of the constructor (with return type void ). For example, here is how a native method can create a FileOutputStream object:

const char[] fileName = ". . ."; 
jstring str_fileName = (*env)->NewStringUTF(env, fileName); 
jclass class_FileOutputStream = (*env)->FindClass(env, 
   "java/io/FileOutputStream"); 
jmethodID id_FileOutputStream = (*env)->GetMethodID(env, 
   class_FileOutputStream, "<init>", "(Ljava/lang/String;)V"); 
jobject obj_stream = (*env)->NewObject(env, 
   class_FileOutputStream, id_FileOutputStream, str_fileName);

Note that the signature of the constructor takes a parameter of type java.lang.String and has a return type of void.

Alternative Method Invocations

There are several variants of the JNI functions for calling a Java method from native code. These are not as important as the functions that we already discussed, but they are occasionally useful.

The CallNonvirtual Xxx Method functions receive an implicit argument, a method ID, a class object of a superclass of the implicit argument, and explicit arguments. The function calls the version of the method in the superclass, bypassing the normal dynamic dispatch mechanism.

All call functions have versions with suffixes “A” and “V” that receive the explicit parameters in an array or a va_list (as defined in the C header stdarg.h ).

Arrays

All Java array types have corresponding C types, as shown in the following table.

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

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

Inheritance hierarchy of array types

Figure 10-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)->GetObjectArray(env, array, i); 
(*env)->SetObjectArray(env, array, j, x);

While simple, this approach is also clearly inefficient; you want to be able to access array elements directly, especially when doing vector and matrix computations.

The Get Xxx ArrayElements function returns a C pointer to the starting element of the array. As with ordinary strings, you must remember to call the corresponding Release Xxx ArrayElements function to tell the virtual machine when you no longer need that pointer. Here, the type Xxx must be a primitive type, that is, not Object. You can then read and write the array elements directly. However, since the pointer may point to a copy, any changes that you make are guaranteed to be reflected in the Java array only when you call the corresponding Release Xxx ArrayElements function!

Here is a code sample that multiplies all elements in an array of double values by a constant. We obtain a C pointer a into the Java array and then access individual elements as a[i].

jdoubleArray array_a = ...; 
double scaleFactor = ...; 
double* a = (*env)->GetDoubleArrayElements(env, array_a, NULL); 
for (i = 0; i < (*env)->GetArrayLength(env, array_a); i++) 
   a[i] = a[i] * scaleFactor; 
(*env)->ReleaseDoubleArrayElements(env, array_a, 0);

Whether the virtual machine actually copies the array depends on how it allocates arrays and does its garbage collection. For example, boolean arrays are represented as packed arrays in Sun’s Java Virtual Machine, and they need to be copied into unpacked arrays of jboolean values. Some “copying” garbage collectors routinely move objects around and update object references. That strategy is not compatible with “pinning” an array to a particular location because the collector cannot update the pointer values in native code. However, the garbage collector in the current virtual machine supplied with the JDK is not a copying collector, which means that currently non-boolean arrays are not copied.

If you want to access a few elements of a large array, use the Get Xxx ArrayRegion and Set Xxx ArrayRegion methods that copy a range of elements from the Java array into a C array and back.

You can create new Java arrays in native methods with the New Xxx Array function. To create a new array of objects, you specify the length, the type of the array elements, and an initial element for all entries (typically, NULL ). Here is an example.

jclass class_Employee = (*env)->FindClass(env, "Employee"); 
jobjectArray array_e = (*env)->NewObjectArray(env, 100, 
   class_Employee, NULL);

Arrays of primitive types are simpler. You just supply the length of the array.

jdoubleArray array_d = (*env)->NewDoubleArray(env, 100);

The array is then filled with zeroes.

Error-Handling

Native methods are a significant security risk to Java programs. The C run-time system has no protection against array bounds errors, indirection through bad pointers, and so on. It is particularly important that programmers of native methods handle all error conditions in order to preserve the integrity of Java. In particular, when your native method diagnoses a problem that it cannot handle, then it should report this problem to the Java Virtual Machine. In Java, you would naturally throw an exception in this situation. However, C has no exceptions. Instead, you must call the Throw or ThrowNew function in order to create a new exception object. When the native method exits, Java will throw that exception.

To use the Throw function, call NewObject to create an object of a subtype of Throwable. For example, here we allocate an EOFException object and throw it.

jclass class_EOFException = (*env)->FindClass(env, 
   "java/io/EOFException"); 
jmethodID id_EOFException = (*env)->GetMethodID(env, 
   class_EOFException, 
   "<init>", "()V"); /* ID of default constructor */ 
jthrowable obj_exc = (*env)->NewObject(env, class_EOFException, 
   id_EOFException); 
(*env)->Throw(env, obj_exc);

It is usually more convenient to call ThrowNew, which constructs an exception object, given a class and an UTF string.

(*env)->ThrowNew(env, (*env)->FindClass(env, 
   "java/io/EOFException"), 
   "Unexpected end of file");

Both Throw and ThrowNew merely post the exception; they do not interrupt the control flow of the native method. Only when the method returns is the Java exception thrown by the virtual machine. Therefore, every call to Throw and ThrowNew should always immediately be followed by a return statement.

C++ NOTE

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 C++ and Java exceptions—unfortunately, this is not currently implemented. You need to use SignalError to throw a Java exception, and you need to make sure that your native methods throw no C++ exceptions.

Normally, native code need not be concerned with catching Java exceptions. However, when a native method calls a Java method, that method might throw an exception. Moreover, a number of the JNI functions throw exceptions as well. For example, SetObjectArrayElement throws an ArrayIndexOutOfBoundsException if the index is out of bounds and an ArrayStoreException if the class of the stored object is not a subclass of the element class of the array. In situations like these, a native method should call the ExceptionOccurred method to determine whether an exception has been thrown. The call

jthrowable obj_exc = (*env)->ExceptionOccurred(env);

returns NULL if no exception is pending, or a reference to the current exception object. Normally, a native method should simply return when an exception has occurred so that the virtual machine can propagate it to the Java code. However, a native method may analyze the exception object to determine if it can handle the exception. If it can, then the function

(*env)->ExceptionClear(env);

must be called to turn off the exception.

In our next example, we implement the fprint native method with the paranoia that is appropriate for a native method. Here are the exceptions that we throw:

  • A NullPointerException if the format string is NULL

  • An IllegalArgumentException if the format string doesn’t contain a % specifier that is appropriate for printing a double

  • An OutOfMemoryError if the call to malloc fails

Finally, to demonstrate how to check for an exception when calling a Java method from a native method, we send the string to the stream, a character at a time, and call ExceptionOccurred after each call. Example 10-11 shows the code for the native method. Notice that the native method does not immediately terminate when an exception occurs in the call to PrintWriter.print —it first frees the cstr buffer. When the native method returns, the virtual machine again raises the exception.

Example 10-11. Printf4.c

#include "Printf4.h" 
#include <string.h> 
#include <stdlib.h> 
#include <float.h> 

char* find_format(const char format[]) 
/** 
 * @param format a string containing a printf format specifier 
 * (such as "%8.2f"). Substrings "%%" are skipped. 
 * @return a pointer to the format specifier (skipping the '%') 
 * or NULL if there wasn't a unique format specifier 
 */ 
{  char* p; 
   char* q; 
   size_t n; 

   p = strchr(format, '%'), 
   while (p != NULL && *(p + 1) == '%') /* skip %% */ 
      p = strchr(p + 2, '%'), 
   if (p == NULL) return NULL; 
   /* now check that % is unique */ 
   p++; 
   q = strchr(p, '%'), 
   while (q != NULL && *(q + 1) == '%') /* skip %% */ 
      q = strchr(q + 2, '%'), 
   if (q != NULL) return NULL; /* % not unique */ 
   q = p + strspn(p, " -0+#"); /* skip past flags */ 
   q += strspn(q, "0123456789"); /* skip past field width */ 
   if (*q == '.') { q++; q += strspn(q, "0123456789"); } 
      /* skip past precision */ 
   if (strchr("eEfFgG", *q) == NULL) return NULL; 
      /* not a floating point format */ 
   return p; 
} 

JNIEXPORT void JNICALL Java_Printf4_fprint 
   (JNIEnv* env, jclass cl, jobject out, jstring format, 
   jdouble x) 
{  const char* cformat; 
   char* fmt; 
   jclass class_PrintWriter; 
   jmethodID id_print; 
   char* cstr; 
   int width; 
   int i; 

   if (format == NULL) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, 
         "java/lang/NullPointerException"), 
         "Printf4.fprint: format is null"); 
      return; 
   } 

   cformat = (*env)->GetStringUTFChars(env, format, NULL); 
   fmt = find_format(cformat); 

   if (fmt == NULL) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, 
         "java/lang/IllegalArgumentException"), 
         "Printf4.fprint: format is invalid"); 
      return; 
   } 
   width = atoi(fmt); 
   if (width == 0) width = DBL_DIG + 10; 
   cstr = (char*)malloc(strlen(cformat) + width); 

   if (cstr == NULL) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "java/lang/OutOfMemoryError"), 
         "Printf4.fprint: malloc failed"); 
      return; 
   } 

   sprintf(cstr, cformat, x); 

   (*env)->ReleaseStringUTFChars(env, format, cformat); 

   /* now call ps.print(str) */ 

   /* get the class */ 
   class_PrintWriter = (*env)->GetObjectClass(env, out); 

   /* get the method ID */ 
   id_print = (*env)->GetMethodID(env, class_PrintWriter, 
      "print", "(C)V"); 

   /* call the method */ 
   for (i = 0; cstr[i] != 0 && !(*env)->ExceptionOccurred(env); 
            i++) 
      (*env)->CallVoidMethod(env, out, id_print, cstr[i]); 

   free(cstr); 
}

Example 10-12. Printf4.java

import java.io.*; 

class Printf4 
{  public static native void fprint(PrintWriter ps, 
      String format, double x); 
   static 
   {  System.loadLibrary("Printf4"); 
   } 
}

The test program in Example 10-13 demonstrates how the native method will throw an exception when the formatting string is not valid.

Example 10-13. Printf4Test.java

import java.io.*; 

class Printf4Test 
{  public static void main(String[] args) 
   {  double price = 44.95; 
      double tax = 7.75; 
      double amountDue = price * (1 + tax / 100); 
      PrintWriter out = new PrintWriter(System.out); 
      Printf4.fprint(out, "Amount due = %%8.2f
", amountDue); 
      out.flush(); 
   } 
}

The Invocation API

Up to now, we have considered Java programs that made a few C calls, presumably because C was faster or allowed access to functionality that was inaccessible from Java. Suppose you are in the opposite situation. You have a C or C++ program and would like to make a few calls to Java code, perhaps because the Java code is easier to program. Of course, you know how to call the Java methods. But you still need to add the Java Virtual Machine to your program so that the Java code can be interpreted. The so-called invocation API is used to embed the Java Virtual Machine into a C or C++ program. Here is the code that you need to initialize a virtual machine.

JavaVM *jvm; 
JNIEnv *env; 
JDK1_1InitArgs vm_args; 
vm_args.version = 0x00010001; 
JNI_GetDefaultJavaVMInitArgs(&vm_args); 
vm_args.classpath = getenv("CLASSPATH"); 
JNI_CreateJavaVM(&jvm, &env, &vm_args);

You need to set up an initialization arguments structure and set the version to Java 1.1. Call JNI_GetDefaultJavaVMInitArgs to have the remaining fields in the initialization structure set to their defaults. Then, set the class path and call JNI_CreateJavaVM to create the virtual machine. That call fills in a pointer jvm to the virtual machine and a pointer env to the execution environment.

Once you have set up the Virtual Machine, from that point on, you can call Java methods in he way described in the preceding sections: simply use the env pointer in the usual way. You need the jvm pointer only to call other functions in the invocation API. Currently, there are only three such functions. The most important one is the function to terminate the virtual machine:

(*jvm)->DestroyJavaVM(jvm);

The C program in Example 10-14 sets up a virtual machine and then calls the main method of the Welcome class which was discussed in Chapter 2 of Volume 1.

Example 10-14. InvocationTest.c

#include <jni.h> 
#include <stdlib.h> 

int main() 
{  JavaVM *jvm; 
   JNIEnv *env; 
   JDK1_1InitArgs vm_args; 
   jclass cls; 
   jmethodID id; 
      vm_args.version = 0x00010001; 
      JNI_GetDefaultJavaVMInitArgs(&vm_args); 
      vm_args.classpath = getenv("CLASSPATH"); 

      JNI_CreateJavaVM(&jvm, &env, &vm_args); 

      cls = (*env)->FindClass(env, "Welcome"); 
      id = (*env)->GetStaticMethodID(env, cls, "main", 
         "([Ljava/lang/String;)V"); 
      (*env)->CallStaticVoidMethod(env, cls, id, 100); 

      (*jvm)->DestroyJavaVM(jvm); 

      return 0; 
}

To compile this program under Windows, you use the command line

cl -Ic:jdkinclude -Ic:jdkincludewin32 InvocationTest.c 
c:jdklibjavai.lib

NOTE

NOTE

When running this program under Windows, make sure that your class path contains c:jdklib classes.zip , not just c:jdklib. Otherwise, the class loader will not find the standard Java library.

Suppose you want to deliver this program to your customers. You need to supply the following items:

  • Your executable program (InvocationTest.exe )

  • The class files of your Java code (Welcome.class )

  • The class files of the Java standard library (classes.zip )

  • The Java native code library (javai.dll )

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 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 [1], it is a data depository that is accessed by the Windows 95 and NT operating systems, and that is available as a storage area for application programs. In older versions of Windows, the operating system as well as applications used so-called INI files to store configuration parameters. Windows 95 and NT programs are supposed to use the registry, instead. The registry has a number of advantages.

  • INI files store data as strings; the registry supports other data types such as integers and byte arrays.

  • INI file sections cannot have subsections; the registry supports a complete tree structure.

  • Configuration parameters were distributed over many INI files; by placing them into the registry, there is a single point for administration and backup.

Admittedly, the registry is also a single point of failure—if you mess up the registry, your computer may malfunction or even fail to boot! The sample program that we present in this section is safe, but if you plan to make any modifications to it, you should learn how to back up the registry before proceeding. See the book by Petrusha mentioned in the footnote for information on how to back up the Windows registry. (Also, programs like Norton Utilities come with user-friendly programs for registry backup.)

The principal tool for inspecting the registry is the registry editor. Because of the potential for error by naive but enthusiastic users, there is no icon for launching the registry editor. Instead, start a DOS shell (or open the Start|Run box) and type regedit. Figure 10-3 shows the registry editor in action.

The registry editor

Figure 10-3. The registry editor

The left-hand side shows the keys, which are arranged in a tree structure. Note that each key starts with one of the HKEY nodes like

HKEY_CLASSES_ROOT 
HKEY_CURRENT_USER 
HKEY_LOCAL_MACHINE 
. . .

The right-hand side shows the name/value pairs that are associated with a particular key. For example, the key

HKEY_CURRENT_USERSoftwareMicrosoftMS Setup (ACME)User Info

has two name/value pairs, namely,

DefCompany="your organization" 
DefName="your name"

In this case, the values are strings. The values can also be integers or arrays of bytes.

A Java 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 class that encapsulates a registry key:

public class Win32RegKey 
{  public Win32RegKey(int theRoot, String thePath) { . . . } 
   public Enumeration names() { . . . } 
   public native Object getValue(String name); 
   public native void setValue(String name, Object value); 

   public static final int HKEY_CLASSES_ROOT = 0x80000000; 
   public static final int HKEY_CURRENT_USER = 0x80000001; 
   public static final int HKEY_LOCAL_MACHINE = 0x80000002; 
   . . . 
}

The names method returns an enumeration that holds all the names stored with the key. You can get at them with the familiar hasMoreElements/nextElement methods. The getValue method returns an object that is either a string, an Integer object, or a byte array. The value parameter of the setValue method must also be of one of these three types.

Here is a simple function that lists the strings that are stored with the key:

HKEY_CURRENT_USERSoftwareMicrosoftMS Setup (ACME)User Info 
public static void main(String[] args) 
{  Win32RegKey key = new Win32RegKey(
      Win32RegKey.HKEY_CURRENT_USER, 
      "Software\Microsoft\MS Setup (ACME)\User Info"); 

   Enumeration enum = key.names(); 

   while (enum.hasMoreElements()) 
   {  String name = (String)enum.nextElement(); 
      System.out.println(name + " = " + key.getValue(name)); 
   } 
}

A typical output of this program is as follows:

DefCompany = Horstmann Software 
DefUser = Cay Horstmann

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 and C strings and arrays. And you saw how to raise a Java exception in case something goes wrong.

Two issues make these native methods more complex than the preceding examples. The getValue and setValue methods deal with the type Object, which can be one of String, Integer, or byte[]. And the enumeration object needs to store the state between successive calls to hasMoreElements/nextElement.

Let us first look at the getValue method. The code (which is shown in Example 10-14) goes through the following steps.

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

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

  3. Read the data into a buffer.

  4. If the type is REG_SZ (a string), then call NewStringUTF to create a new string with the value data.

  5. If the type is REG_DWORD (a 32-bit integer), then invoke the Integer constructor.

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

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

  8. Close the key and return the object (String, Integer, or byte[] ) that had been created.

As you can see, this example illustrates quite nicely how to generate Java objects of different types.

In this native method, it was not difficult to cope with the generic return type. The jstring, jobject, or jarray reference was simply returned as a jobject. However, the setValue method receives a reference to an Object, and it must determine its exact type so it can save it as either a string, integer, or byte array. We can make this determination by querying the class of the value object, finding the class references for java.lang.String, java.lang.Integer, and byte[], and comparing them with the IsAssignableFrom function.

If class1 and class2 are two class references, then the call

(*env)->IsAssignableFrom(env, class1, class2)

returns JNI_TRUE when class1 and class2 are the same class or class1 is a subclass of class2. In either case, references to objects of class1 can be cast to class2. For example, when

(*env)->IsAssignableFrom(env, 
   (*env)->GetObjectClass(env, value) 
   (*env)->FindClass(env, "[B"))

is true, then we know that value is a byte array.

Here is an overview of the code of the setValue method.

  1. Open the registry key for writing.

  2. Find the type of the value to write.

  3. If the type is String, call GetStringUTFChars to get a pointer to the characters. Also, obtain the string length.

  4. If the type is Integer, call the intValue method to get the integer stored in the wrapper object.

  5. If the type is byte[], call GetByteArrayElements to get a pointer to the bytes. Also, obtain the string length.

  6. Pass the data and length to the registry.

  7. Close the key. If the type is String or byte[], then also release the pointer to the characters or bytes.

Finally, let us turn to the native methods that enumerate keys. These are methods of the Win32RegKeyNameEnumeration class (see Example 10-14). When the enumeration process starts, we must open the key. For the duration of the enumeration, we must retain the key handle. That is, the key handle must be stored with the enumeration object. The key handle is of type DWORD, a 32-bit quantity, and, hence, can be stored in a Java integer. It is stored in the hkey field of the enumeration class. When the enumeration starts, the field is initialized with SetIntField. Subsequent calls read the value with GetIntField.

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.

  1. Retrieve the index and count fields.

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

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

The nextElement method needs to work a little harder.

  1. Retrieve the index and count fields.

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

  3. If index equals count, throw a NoSuchElementException.

  4. Read the next name from the registry.

  5. Increment index.

  6. If index equals count, close the key.

To compile the program, you must link in the advapi32.lib library. Before compiling, remember to run javah on both Win32RegKey and Win32RegKeyNameEnumeration. The complete command line is

cl -Ic:jdkinclude -Ic:jdkincludewin32 -LD 
   Win32RegKey.c advapi32.lib -FeWin32RegKey.dll

Example 10-14 shows a program to test our new registry functions. We add three name/value pairs, a string, an integer, and a byte array to the key.

HKEY_CURRENT_USERSoftwareMicrosoftMS Setup (ACME)User Info

We then enumerate all names of that key and retrieve their values. The program should print out

DefName = Cay Horstmann 
DefCompany = Horstmann Software 
Default user = Bozo the clown 
Lucky number = 13 
Small primes = 2 3 5 7 11 13

Although adding these name/value pairs to that key probably does no harm, you may want to use the registry editor to remove them after running this program.

Example 10-15. Win32RegKey.java

import java.util.*; 

public class Win32RegKey 
{  public Win32RegKey(int theRoot, String thePath) 
   {  root = theRoot; 
      path = thePath; 
   } 
   public Enumeration names() 
   {  return new Win32RegKeyNameEnumeration(root, path); 
   } 
   public native Object getValue(String name); 
   public native void setValue(String name, Object value); 

   public static final int HKEY_CLASSES_ROOT = 0x80000000; 
   public static final int HKEY_CURRENT_USER = 0x80000001; 
   public static final int HKEY_LOCAL_MACHINE = 0x80000002; 
   public static final int HKEY_USERS = 0x80000003; 
   public static final int HKEY_CURRENT_CONFIG = 0x80000005; 
   public static final int HKEY_DYN_DATA = 0x80000006; 

   private int root; 
   private String path; 

   static 
   {  System.loadLibrary("Win32RegKey"); 
   } 
} 

class Win32RegKeyNameEnumeration implements Enumeration 
{  Win32RegKeyNameEnumeration(int theRoot, String thePath) 
   {  root = theRoot; 
      path = thePath; 
   } 
   public native Object nextElement(); 
   public native boolean hasMoreElements(); 

   private int root; 
   private String path; 
   private int index = -1; 
   private int hkey = 0; 
   private int maxsize; 
   private int count; 
} 

class Win32RegKeyException extends RuntimeException 
{  public Win32RegKeyException() {} 
   public Win32RegKeyException(String why) 
   {  super(why); 
   } 
}

Example 10-16. Win32RegKey.c

#include "Win32RegKey.h" 
#include "Win32RegKeyNameEnumeration.h" 
#include <string.h> 
#include <stdlib.h> 
#include <windows.h> 

JNIEXPORT jobject JNICALL Java_Win32RegKey_getValue 
   (JNIEnv* env, jobject this_obj, jstring name) 
{  const char* cname; 
   jstring path; 
   const char* cpath; 
   HKEY hkey; 
   DWORD type; 
   DWORD size; 
   jclass this_class; 
   jfieldID id_root; 
   jfieldID id_path; 
   HKEY root; 
   jobject ret; 
   char* cret; 

   /* get the class */ 
   this_class = (*env)->GetObjectClass(env, this_obj); 

   /* get the field IDs */ 
   id_root = (*env)->GetFieldID(env, this_class, "root", "I"); 
   id_path = (*env)->GetFieldID(env, this_class, "path", 
      "Ljava/lang/String;"); 
   /* get the fields */ 
   root = (HKEY)(*env)->GetIntField(env, this_obj, id_root); 
   path = (jstring)(*env)->GetObjectField(env, this_obj, 
      id_path); 
   cpath = (*env)->GetStringUTFChars(env, path, NULL); 

   /* open the registry key */ 
   if (RegOpenKeyEx(root, cpath, 0, KEY_READ, &hkey) 
      != ERROR_SUCCESS) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Open key failed"); 
      (*env)->ReleaseStringUTFChars(env, path, cpath); 
      return NULL; 
   } 

   (*env)->ReleaseStringUTFChars(env, path, cpath); 
   cname = (*env)->GetStringUTFChars(env, name, NULL); 

   /* find the type and size of the value */ 
   if (RegQueryValueEx(hkey, cname, NULL, &type, NULL, &size) != 
      ERROR_SUCCESS) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Query value key failed"); 
      RegCloseKey(hkey); 
      (*env)->ReleaseStringUTFChars(env, name, cname); 
      return NULL; 
   } 

   /* get memory to hold the value */ 
   cret = (char*)malloc(size); 

   /* read the value */ 
   if (RegQueryValueEx(hkey, cname, NULL, &type, cret, &size) != 
      ERROR_SUCCESS) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Query value key failed"); 
      free(cret); 
      RegCloseKey(hkey); 
      (*env)->ReleaseStringUTFChars(env, name, cname); 
      return NULL; 
   } 

   /* depending on the type, store the value in a string, 
      integer or byte array */ 
   if (type == REG_SZ) 
   {  ret = (*env)->NewStringUTF(env, cret); 
   } 
   else if (type == REG_DWORD) 
   {  jclass class_Integer = (*env)->FindClass(env, 
         "java/lang/Integer"); 
      /* get the method ID of the constructor */ 
      jmethodID id_Integer = (*env)->GetMethodID(env, 
         class_Integer, "<init>", "(I)V"); 
      int value = *(int*)cret; 
      /* invoke the constructor */ 
      ret = (*env)->NewObject(env, class_Integer, id_Integer, 
         value); 
   } 
   else if (type == REG_BINARY) 
   {  ret = (*env)->NewByteArray(env, size); 
      (*env)->SetByteArrayRegion(env, (jarray)ret, 0, size, 
         cret); 
   } 
   else 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Unsupported value type"); 
      ret = NULL; 
   } 

   free(cret); 
   RegCloseKey(hkey); 
   (*env)->ReleaseStringUTFChars(env, name, cname); 

   return ret; 
} 

JNIEXPORT void JNICALL Java_Win32RegKey_setValue 
   (JNIEnv* env, jobject this_obj, jstring name, jobject value) 
{  const char* cname; 
   jstring path; 
   const char* cpath; 
   HKEY hkey; 
   DWORD type; 
   DWORD size; 
   jclass this_class; 
   jclass class_value; 
   jclass class_Integer; 
   jfieldID id_root; 
   jfieldID id_path; 
   HKEY root; 
   const char* cvalue; 
   int ivalue; 
   /* get the class */ 
   this_class = (*env)->GetObjectClass(env, this_obj); 

   /* get the field IDs */ 
   id_root = (*env)->GetFieldID(env, this_class, "root", "I"); 
   id_path = (*env)->GetFieldID(env, this_class, "path", 
      "Ljava/lang/String;"); 

   /* get the fields */ 
   root = (HKEY)(*env)->GetIntField(env, this_obj, id_root); 
   path = (jstring)(*env)->GetObjectField(env, this_obj, 
      id_path); 
   cpath = (*env)->GetStringUTFChars(env, path, NULL); 

   /* open the registry key */ 
   if (RegOpenKeyEx(root, cpath, 0, KEY_WRITE, &hkey) 
      != ERROR_SUCCESS) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Open key failed"); 
      (*env)->ReleaseStringUTFChars(env, path, cpath); 
      return; 
   } 

   (*env)->ReleaseStringUTFChars(env, path, cpath); 
   cname = (*env)->GetStringUTFChars(env, name, NULL); 

   class_value = (*env)->GetObjectClass(env, value); 
   class_Integer = (*env)->FindClass(env, "java/lang/Integer"); 
   /* determine the type of the value object */ 
   if ((*env)->IsAssignableFrom(env, class_value, 
      (*env)->FindClass(env, "java/lang/String"))) 
   {  /* it is a string--get a pointer to the characters */ 
      cvalue = (*env)->GetStringUTFChars(env, (jstring)value, 
         NULL); 
      type = REG_SZ; 
      size = (*env)->GetStringLength(env, (jstring)value) + 1; 
   } 
   else if ((*env)->IsAssignableFrom(env, class_value, 
      class_Integer)) 
   {  /* it is an integer--call intValue to get the value */ 
      jmethodID id_intValue = (*env)->GetMethodID(env, 
         class_Integer, "intValue", "()I"); 
      ivalue = (*env)->CallIntMethod(env, value, id_intValue); 
      type = REG_DWORD; 
      cvalue = (char*)&ivalue; 
      size = 4; 
   } 
   else if ((*env)->IsAssignableFrom(env, class_value, 
      (*env)->FindClass(env, "[B"))) 
   {  /* it is a byte array--get a pointer to the bytes */ 
      type = REG_BINARY; 
      cvalue = (char*)(*env)->GetByteArrayElements(env, 
         (jarray)value, NULL); 
      size = (*env)->GetArrayLength(env, (jarray)value); 
   } 
   else 
   {  /* we don't know how to handle this type */ 
      (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Unsupported value type"); 
      RegCloseKey(hkey); 
      (*env)->ReleaseStringUTFChars(env, name, cname); 
      return; 
   } 

   /* set the value */ 
   if (RegSetValueEx(hkey, cname, 0, type, cvalue, size) 
      != ERROR_SUCCESS) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Query value key failed"); 
   } 

   RegCloseKey(hkey); 
   (*env)->ReleaseStringUTFChars(env, name, cname); 

   /* if the value was a string or byte array, release the 
      pointer */ 
   if (type == REG_SZ) 
   {  (*env)->ReleaseStringUTFChars(env, (jstring)value, 
         cvalue); 
   } 
   else if (type == REG_BINARY) 
   {  (*env)->ReleaseByteArrayElements(env, (jarray)value, 
         (byte*)cvalue, 0); 
   } 
} 

static int startNameEnumeration(JNIEnv* env, jobject this_obj, 
   jclass this_class) 
/* helper function to start enumeration of names 
*/ 
{  jfieldID id_index; 
   jfieldID id_count; 
   jfieldID id_root; 
   jfieldID id_path; 
   jfieldID id_hkey; 
   jfieldID id_maxsize; 

   HKEY root; 
   jstring path; 
   const char* cpath; 
   HKEY hkey; 
   int maxsize = 0; 
   int count = 0; 

   /* get the field IDs */ 
   id_root = (*env)->GetFieldID(env, this_class, "root", "I"); 
   id_path = (*env)->GetFieldID(env, this_class, "path", 
      "Ljava/lang/String;"); 
   id_hkey = (*env)->GetFieldID(env, this_class, "hkey", "I"); 
   id_maxsize = (*env)->GetFieldID(env, this_class, "maxsize", 
      "I"); 
   id_index = (*env)->GetFieldID(env, this_class, "index", 
      "I"); 
   id_count = (*env)->GetFieldID(env, this_class, "count", 
      "I"); 

   /* get the field values */ 
   root = (HKEY)(*env)->GetIntField(env, this_obj, id_root); 
   path = (jstring)(*env)->GetObjectField(env, this_obj, 
      id_path); 
   cpath = (*env)->GetStringUTFChars(env, path, NULL); 

   /* open the registry key */ 
   if (RegOpenKeyEx(root, cpath, 0, KEY_READ, &hkey) 
      != ERROR_SUCCESS) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Open key failed"); 
      (*env)->ReleaseStringUTFChars(env, path, cpath); 
      return -1; 
   } 
   (*env)->ReleaseStringUTFChars(env, path, cpath); 

   /* query count and max length of names */ 
   if (RegQueryInfoKey(hkey, NULL, NULL, NULL, NULL, 
      NULL, NULL, &count, &maxsize, NULL, NULL, NULL) 
      != ERROR_SUCCESS) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Query info key failed"); 
      return -1; 
   } 
   /* set the field values */ 
   (*env)->SetIntField(env, this_obj, id_hkey, (DWORD)hkey); 
   (*env)->SetIntField(env, this_obj, id_maxsize, maxsize + 1); 
   (*env)->SetIntField(env, this_obj, id_index, 0); 
   (*env)->SetIntField(env, this_obj, id_count, count); 
   return count; 
} 

JNIEXPORT jboolean JNICALL 
   Java_Win32RegKeyNameEnumeration_hasMoreElements 
   (JNIEnv* env, jobject this_obj) 
{  jclass this_class; 
   jfieldID id_index; 
   jfieldID id_count; 
   int index; 
   int count; 
   /* get the class */ 
   this_class = (*env)->GetObjectClass(env, this_obj); 

   /* get the field IDs */ 
   id_index = (*env)->GetFieldID(env, this_class, "index", 
      "I"); 
   id_count = (*env)->GetFieldID(env, this_class, "count", 
      "I"); 

   index = (*env)->GetIntField(env, this_obj, id_index); 
   if (index == -1) /* first time */ 
   {  count = startNameEnumeration(env, this_obj, this_class); 
      index = 0; 
   } 
   else 
      count = (*env)->GetIntField(env, this_obj, id_count); 
   return index < count; 
} 

JNIEXPORT jobject JNICALL 
   Java_Win32RegKeyNameEnumeration_nextElement 
   (JNIEnv* env, jobject this_obj) 
{  jclass this_class; 
   jfieldID id_index; 
   jfieldID id_hkey; 
   jfieldID id_count; 
   jfieldID id_maxsize; 

   HKEY hkey; 
   int index; 
   int count; 
   int maxsize; 
   char* cret; 
   jstring ret; 

  /* get the class */ 
   this_class = (*env)->GetObjectClass(env, this_obj); 

   /* get the field IDs */ 
   id_index = (*env)->GetFieldID(env, this_class, "index", 
      "I"); 
   id_count = (*env)->GetFieldID(env, this_class, "count", 
      "I"); 
   id_hkey = (*env)->GetFieldID(env, this_class, "hkey", "I"); 
   id_maxsize = (*env)->GetFieldID(env, this_class, "maxsize", 
      "I"); 

   index = (*env)->GetIntField(env, this_obj, id_index); 
   if (index == -1) /* first time */ 
   {  count = startNameEnumeration(env, this_obj, this_class); 
      index = 0; 
   } 
   else 
      count = (*env)->GetIntField(env, this_obj, id_count); 

   if (index >= count) /* already at end */ 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, 
         "java/util/NoSuchElementException"), 
         "past end of enumeration"); 
      return NULL; 
   } 

   maxsize = (*env)->GetIntField(env, this_obj, id_maxsize); 
   hkey = (HKEY)(*env)->GetIntField(env, this_obj, id_hkey); 
   cret = (char*)malloc(maxsize); 

   /* find the next name */ 
   if (RegEnumValue(hkey, index, cret, &maxsize, NULL, NULL, 
      NULL, NULL) != ERROR_SUCCESS) 
   {  (*env)->ThrowNew(env, 
         (*env)->FindClass(env, "Win32RegKeyException"), 
         "Enum value failed"); 
      free(cret); 
      RegCloseKey(hkey); 
      (*env)->SetIntField(env, this_obj, id_index, count); 
      return NULL; 
   } 
   ret = (*env)->NewStringUTF(env, cret); 
   free(cret); 

   /* increment index */ 
   index++; 
   (*env)->SetIntField(env, this_obj, id_index, index); 

   if (index == count) /* at end */ 
   {  RegCloseKey(hkey); 
   } 

   return ret; 
}

Example 10-17. Win32RegKeyTest.java

import java.util.*; 

public class Win32RegKeyTest 
{  public static void main(String[] args) 
   {  Win32RegKey key = new Win32RegKey(
         Win32RegKey.HKEY_CURRENT_USER, 
         "Software\Microsoft\MS Setup (ACME)\User Info"); 

      key.setValue("Default user", "Bozo the clown"); 
      key.setValue("Lucky number", new Integer(13)); 
      key.setValue("Small primes", new byte[] 
         { 2, 3, 5, 7, 11 }); 

      Enumeration enum = key.names(); 

      while (enum.hasMoreElements()) 
      {  String name = (String)enum.nextElement(); 
         System.out.print(name + " = "); 

         Object value = key.getValue(name); 

         if (value instanceof byte[]) 
         {  byte[] bvalue = (byte[])value; 
            for (int i = 0; i < bvalue.length; i++) 
               System.out.print((bvalue[i] & 0xFF) + " "); 
         } 
         else System.out.print(value); 

         System.out.println(); 
      } 
   } 
}

NOTE

NOTE

Microsoft has a method it calls the Raw Native Interface (http://www.microsoft.com/java) that is actually fairly close to the method used for calling native functions in the JDK 1.0. As far as we can tell, it has no special virtues; we wouldn’t bother with it.

On the other hand, Microsoft and Apple both have announced another approach to calling native methods called JDirect. (www.microsoft.com/java and http://applejava.apple.com/MRJGoodies) Both approaches are based on special virtual machines that handle a lot more of the busy work needed to call native methods than does the standard JVM in the JDK.

In particular, both versions of JDirect would allow you to dispense with the stubs produced by javah and to access native methods contained in a DLL without using any C code whatsoever. The JDirect specification is in flux, but it is potentially very interesting. (Keep in mind that using it ties you to using Microsoft’s virtual machine or Apple’s MRJ.)

For example, Microsoft’s version of JDirect essentially makes calling a Windows-based DLL trivial. Based on the current specification, your code for accessing the Registry function under JDirect could be as simple as:

class Registry 
{
/** @dll.import("advapi32") */ 
private static native int RegCreateKey(int hkey, String 
subKey, int result);} 
//other registry functions follow 


}

where the @dll.import will tell Microsoft’s JVM to automatically make whatever type conversions need to be made.

Based on the limited information at hand, the Apple version would be something like:

import com.apple.NativeObject; 
public class MakeBeeps implements NativeObject 
{   public native static void SysBeep(short duration); 
   private static String[] nativeNames = { "InterfaceLib" 
}; 
}

Both of these methods are clearly much simpler than the methods used in the JDK, but whether they will be uniformly adopted by all virtual machines on Windows or on the Apple is exceedingly doubtful. If you use Java as a nice programming language for platform-specific programming, you will find these direct call interfaces quite interesting. If you use Java to write platform-independent code, you will likely pass on them.



[1] A good book is Inside the Windows 95 Registryby Ron Petrusha [O’Reilly, 1996].

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

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