A Complete Java Example: Accessing the Windows Registry

In this section, we describe a full, working example that covers everything we discussed in this chapter: using native methods with strings, arrays, objects, constructor calls, and error handling. We’ll show you how to put a Java plat­form wrapper around a subset of the ordinary C-based APIs used to work with the Windows registry. Of course, the Windows registry being a Windows- specific feature, such a program 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.

1. Overview of the Windows Registry

The Windows registry is a data depository that holds configuration information for the Windows operating system and application programs. It provides a single point for administration and backup of system and application prefer­ences. On the downside, the registry is also a single point of failure—if you mess up the registry, your computer could malfunction or even fail to boot!

We don’t suggest that you use the registry to store configuration parameters for your Java programs. The Java preferences API is a better solution (see Volume I, Chapter 10 for more information). We’ll simply use the registry to demonstrate how to wrap a nontrivial native API into a Java class.

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

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

HKEY_CLASSES_ROOT

HKEY_CURRENT_USER

HKEY_LOCAL_MACHINE

The right side shows the name/value pairs associated with a particular key. For example, if you installed Java 11, the key

HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment

contains a name/value pair such as

CurrentVersion=”11.0_10″

In this case, the value is a string. The values can also be integers or arrays of bytes.

2. A Java Platform Interface for Accessing the Registry

We create 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 some important operations such as adding, deleting, and enumerating keys. (It should 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.

3. Implementation of Registry Access Functions as Native Methods

We need to implement three actions:

  • Get the value of a name
  • Set the value of a name
  • Iterate through the names of a key

In this chapter, you have seen essentially all the tools that are required, such as the conversion between Java strings and arrays and those of C. You also saw how to raise a Java exception in case something goes wrong.

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

Let us first look at the getValue method. The method (shown in Listing 12.22) goes through the following steps:

  1. Opens the registry key. To read their values, the registry API requires that keys be open.
  2. Queries the type and size of the value associated with the name.
  3. Reads the data into a buffer.
  4. Calls NewStringUTF to create a new string with the value data if the type is REG_SZ (a string).
  5. Invokes the Integer constructor if the type is REG_DWORD (a 32-bit integer).
  6. Calls NewByteArray to create a new byte array, then SetByteArrayRegion to copy the value data into the byte array, if the type is REG_BINARY.
  7. If the type is none of these or if an error occurred when an API function was called, throws an exception and releases all resources that had been acquired up to that point.
  8. Closes the key and returns the object (String, Integer, or byte[]) that had been created.

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

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

If c!ass1 and dass2 are two class references, then the call

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

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

(*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, value),

(*env)->FindClass(env, “[B”))

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

Here is an overview of the steps in the setValue method:

  1. Open the registry key for writing.
  2. Find the type of the value to write.
  3. Call GetStringUTFChars to get a pointer to the characters if the type is String.
  4. Call the intValue method to get the integer stored in the wrapper object if the type is Integer.
  5. Call GetByteArrayElements to get a pointer to the bytes if the type is byte[].
  6. Pass the data and length to the registry.
  7. Close the key.
  8. Release the pointer to the data if the type is String or byte[].

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

In this example, we store three other data items with the enumeration object. When the enumeration first starts, we can query the registry for the count of name/value pairs and the length of the longest name, which we need so we can allocate C character arrays to hold the names. These values are stored in the count and maxsize fields of the enumeration object. Finally, the index field, initialized with -1 to indicate the start of the enumeration, is set to 0 once the other instance fields are initialized, and is incremented after every enumeration step.

Let’s walk through the native methods that support the enumeration. The hasMoreElements method is simple:

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

Before compiling, remember to run javac -h on both Win32RegKey and Win32RegKeyNameEnumeration. The complete command line for the Microsoft compiler is

 cl -I jdk\include -I jdk\include\win32 -LD Win32RegKey.c advapi32.lib -FeWin32RegKey.dll

With Cygwin, use

 gcc -mno-cygwin -D __int64=”long long” -I jdk\include -I jdk\include\win32 \
-I c:\cygwin\usr\include\w32api -shared -Wl,–add-stdcall-alias -o Win32RegKey.dll
Win32RegKey.c

As the registry API is specific to Windows, this program will not work on other operating systems.

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

HKEY_CURRENT_USER\Software\JavaSoft\Java Runtime Environment

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

Default user=Harry Hacker

Lucky number=13

Small primes=2 3 5 7 11 13

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


You have now reached the end of the second volume of Core Java, completing a long journey in which you encountered many advanced APIs. We started out with topics that every Java programmer needs to know: streams, XML, networking, databases, and internationalization. We concluded with very technical chapters on security, annotation processing, advanced graphics, and native methods. We hope that you enjoyed your tour through the vast breadth of the Java APIs, and that you will be able to apply your newly gained knowledge in your projects.

Source: Horstmann Cay S. (2019), Core Java. Volume II – Advanced Features, Pearson; 11th edition.

Leave a Reply

Your email address will not be published. Required fields are marked *