MindView Inc.
[ Viewing Hints ] [ 2nd Edition ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Java, 1st edition

©1998 by Bruce Eckel

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]

A: Using non-Java code

This appendix was contributed by and used with the permission of Andrea Provaglio (www.AndreaProvaglio.com).

The Java language and its standard API are rich enough to write full-fledged applications. But in some cases you must call non-Java code; for example, if you want to access operating-system-specific features, interface with special hardware devices, reuse a pre-existing, non-Java code base, or implement time-critical sections of code. Interfacing with non-Java code requires dedicated support in the compiler and in the Virtual Machine, and additional tools to map the Java code to the non-Java code. (There’s also a simple approach: in Chapter 15, the section titled “a Web application” contains an example of connecting to non-Java code using standard input and output.) Currently, different vendors offer different solutions: Java 1.1 has the Java Native Interface (JNI), Netscape has proposed its Java Runtime Interface, and Microsoft offers J/Direct, Raw Native Interface (RNI), and Java/COM integration.

This fragmentation among different vendors implies serious drawbacks for the programmer. If a Java application must call native methods, the programmer might need to implement different versions of the native methods depending on the platform the application will run on. The programmer might actually need different versions of the Java code as well as different Java virtual machines.

Another solution is CORBA (Common Object Request Broker Architecture), an integration technology developed by the OMG (Object Management Group, a non-profit consortium of companies). CORBA is not part of any language, but is a specification for implementing a common communication bus and services that allow interoperability among objects implemented in different languages. This communication bus, called an ORB (Object Request Broker), is a product implemented by third-party vendors, but it is not part of the Java language specification.

This appendix gives an overview of JNI, J/Direct, RNI, Java/COM integration, and CORBA. This is not an in-depth treatment, and in some cases you’re assumed to have partial knowledge of the related concepts and techniques. But in the end, you should be able to compare the different approaches and choose the one that is most appropriate to the problem you want to solve.

The Java Native Interface

JNI is a fairly rich programming interface that allows you to call native methods from a Java application. It was added in Java 1.1, maintaining a certain degree of compatibility with its Java 1.0 equivalent, the native method interface (NMI). NMI has design characteristics that make it unsuitable for adoption in all virtual machines. For this reason, future versions of the language might no longer support NMI, and it will not be covered here.

Currently, JNI is designed to interface with native methods written only in C or C++. Using JNI, your native methods can:

Thus, virtually everything you can do with classes and objects in ordinary Java you can also do in native methods.

Calling a native method

We’ll start with a simple example: a Java program that calls a native method, which in turn calls the Win32 MessageBox( ) API function to display a graphical text box. This example will also be used later with J/Direct. If your platform is not Win32, just replace the C header include:

#include <windows.h>

with

#include <stdio.h>

and replace the call to MessageBox( ) with a call to printf( ).

The first step is to write the Java code declaring a native method and its arguments:

class ShowMsgBox {
  public static void main(String [] args) {
    ShowMsgBox app = new ShowMsgBox();
    app.ShowMessage("Generated with JNI");
  }
  private native void ShowMessage(String msg);
  static {
    System.loadLibrary("MsgImpl");
  }
}

The native method declaration is followed by a static block that calls System.loadLibrary( ) (which you could call at any time, but this style is more appropriate). System.loadLibrary( ) loads a DLL in memory and links to it. The DLL must be in your system path or in the directory containing the Java class file. The file name extension is automatically added by the JVM depending on the platform.

The C header file generator: javah

Now compile your Java source file and run javah on the resulting .class file. Javah was present in version 1.0, but since you are using Java 1.1 JNI you must specify the –jni switch:

javah –jni ShowMsgBox

Javah reads the Java class file and for each native method declaration it generates a function prototype in a C or C++ header file. Here’s the output: the ShowMsgBox.h source file (edited slightly to fit into the book):

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

#ifndef _Included_ShowMsgBox
#define _Included_ShowMsgBox
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ShowMsgBox
 * Method:    ShowMessage
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL 
Java_ShowMsgBox_ShowMessage
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

As you can see by the #ifdef __cplusplus preprocessor directive, this file can be compiled either by a C or a C++ compiler. The first #include directive includes jni.h, a header file that, among other things, defines the types that you can see used in the rest of the file. JNIEXPORT and JNICALL are macros that expand to match platform-specific directives; JNIEnv, jobject and jstring are JNI data type definitions.

Name mangling and function signatures

JNI imposes a naming convention (called name mangling) on native methods; this is important, since it’s part of the mechanism by which the virtual machine links Java calls to native methods. Basically, all native methods start with the word “Java,” followed by the name of the class in which the Java native declaration appears, followed by the name of the Java method; the underscore character is used as a separator. If the Java native method is overloaded, then the function signature is appended to the name as well; you can see the native signature in the comments preceding the prototype. For more information about name mangling and native method signatures, please refer to the JNI documentation.

Implementing your DLL

At this point, all you have to do is write a C or C++ source file that includes the javah-generated header file and implements the native method, then compile it and generate a dynamic link library. This part is platform-dependent, and I’ll assume that you know how to create a DLL. The code below implements the native method by calling a Win32 API. It is then compiled and linked into a file called MsgImpl.dll (for “Message Implementation”).

#include <windows.h>
#include "ShowMsgBox.h"

BOOL APIENTRY DllMain(HANDLE hModule, 
  DWORD dwReason, void** lpReserved) {
  return TRUE;
}

JNIEXPORT void JNICALL 
Java_ShowMsgBox_ShowMessage(JNIEnv * jEnv, 
  jobject this, jstring jMsg) {
  const char * msg;
  msg = (*jEnv)->GetStringUTFChars(jEnv, jMsg,0);
  MessageBox(HWND_DESKTOP, msg, 
    "Thinking in Java: JNI",
    MB_OK | MB_ICONEXCLAMATION);
  (*jEnv)->ReleaseStringUTFChars(jEnv, jMsg,msg);
}

If you have no interest in Win32, just skip the MessageBox( ) call; the interesting part is the surrounding code. The arguments that are passed into the native method are the gateway back into Java. The first, of type JNIEnv, contains all the hooks that allow you to call back into the JVM. (We’ll look at this in the next section.) The second argument has a different meaning depending on the type of method. For non-static methods like the example above (also called instance methods), the second argument is the equivalent of the “this” pointer in C++ and similar to this in Java: it’s a reference to the object that called the native method. For static methods, it’s a reference to the Class object where the method is implemented.

The remaining arguments represent the Java objects passed into the native method call. Primitives are also passed in this way, but they come in by value.

In the following sections we’ll explain this code by looking at how to access and control the JVM from inside a native method.

Accessing JNI functions:
The JNIEnv argument

JNI functions are those that the programmer uses to interact with the JVM from inside a native method. As you can see in the example above, every JNI native method receives a special argument as its first parameter: the JNIEnv argument, which is a pointer to a special JNI data structure of type JNIEnv_. One element of the JNI data structure is a pointer to an array generated by the JVM; each element of this array is a pointer to a JNI function. The JNI functions can be called from the native method by dereferencing these pointers (it’s simpler than it sounds). Every JVM provides its own implementation of the JNI functions, but their addresses will always be at predefined offsets.

Through the JNIEnv argument, the programmer has access to a large set of functions. These functions can be grouped into the following categories:

The number of JNI functions is quite large and won’t be covered here. Instead, I’ll show the rationale behind the use of these functions. For more detailed information, consult your compiler’s JNI documentation.

If you take a look at the jni.h header file, you’ll see that inside the #ifdef __cplusplus preprocessor conditional, the JNIEnv_ structure is defined as a class when compiled by a C++ compiler. This class contains a number of inline functions that let you access the JNI functions with an easy and familiar syntax. For example, the line in the preceding example

(*jEnv)->ReleaseStringUTFChars(jEnv, jMsg,msg);

can be rewritten as follows in C++:

jEnv->ReleaseStringUTFChars(jMsg,msg);

You’ll notice that you no longer need the double dereferencing of the jEnv pointer, and that the same pointer is no longer passed as the first parameter to the JNI function call. In the rest of these examples, I’ll use the C++ style.

Accessing Java Strings

As an example of accessing a JNI function, consider the code shown above. Here, the JNIEnv argument jEnv is used to access a Java String. Java Strings are in Unicode format, so if you receive one and want to pass it to a non-Unicode function (printf( ), for example), you must first convert it into ASCII characters with the JNI function GetStringUTFChars( ). This function takes a Java String and converts it to UTF-8 characters. (These are 8 bits wide to hold ASCII values or 16 bits wide to hold Unicode. If the content of the original string was composed only of ASCII, the resulting string will be ASCII as well.)

GetStringUTFChars is the name of one of the fields in the structure that JNIEnv is indirectly pointing to, and this field in turn is a pointer to a function. To access the JNI function, we use the traditional C syntax for calling a function though a pointer. You use the form above to access all of the JNI functions.

Passing and using Java objects

In the previous example we passed a String to the native method. You can also pass Java objects of your own creation to a native method. Inside your native method, you can access the fields and methods of the object that was received.

To pass objects, use the ordinary Java syntax when declaring the native method. In the example below, MyJavaClass has one public field and one public method. The class UseObjects declares a native method that takes an object of class MyJavaClass. To see if the native method manipulates its argument, the public field of the argument is set, the native method is called, and then the value of the public field is printed.

class MyJavaClass {
  public void divByTwo() { aValue /= 2; }
  public int aValue;
}

public class UseObjects {
  public static void main(String [] args) {
    UseObjects app = new UseObjects();
    MyJavaClass anObj = new MyJavaClass();
    anObj.aValue = 2;
    app.changeObject(anObj);
    System.out.println("Java: " + anObj.aValue);
  }
  private native void 
  changeObject(MyJavaClass obj);
  static {
    System.loadLibrary("UseObjImpl");
  }
}

After compiling the code and handing the .class file to javah, you can implement the native method. In the example below, once the field and method ID are obtained, they are accessed through JNI functions.

JNIEXPORT void JNICALL
Java_UseObjects_changeObject(
  JNIEnv * env, jobject jThis, jobject obj) {
  jclass cls;
  jfieldID fid;
  jmethodID mid;
  int value;
  cls = env->GetObjectClass(obj);
  fid = env->GetFieldID(cls,
        "aValue", "I");
  mid = env->GetMethodID(cls,
        "divByTwo", "()V");
  value = env->GetIntField(obj, fid);
  printf("Native: %d\n", value);
  env->SetIntField(obj, fid, 6);
  env->CallVoidMethod(obj, mid);
  value = env->GetIntField(obj, fid);
  printf("Native: %d\n", value);
}

The first argument aside, the C++ function receives a jobject, which is the native side of the Java object reference we pass from the Java code. We simply read aValue, print it out, change the value, call the object’s divByTwo( ) method, and print the value out again.

To access a field or method, you must first obtain its identifier. Appropriate JNI functions take the class object, the element name, and the signature. These functions return an identifier that you use to access the element. This approach might seem convoluted, but your native method has no knowledge of the internal layout of the Java object. Instead, it must access fields and methods through indexes returned by the JVM. This allows different JVMs to implement different internal object layouts with no impact on your native methods.

If you run the Java program, you’ll see that the object that’s passed from the Java side is manipulated by your native method. But what exactly is passed? A pointer or a Java reference? And what is the garbage collector doing during native method calls?

The garbage collector continues to operate during native method execution, but it’s guaranteed that your objects will not be garbage collected during a native method call. To ensure this, local references are created before, and destroyed right after, the native method call. Since their lifetime wraps the call, you know that the objects will be valid throughout the native method call.

Since these references are created and subsequently destroyed every time the function is called, you cannot make local copies in your native methods, in static variables. If you want a reference that lasts across function invocations, you need a global reference. Global references are not created by the JVM, but the programmer can make a global reference out of a local one by calling specific JNI functions. When you create a global reference, you become responsible for the lifetime of the referenced object. The global reference (and the object it refers to) will be in memory until the programmer explicitly frees the reference with the appropriate JNI function. It’s similar to malloc( ) and free( ) in C.

JNI and Java exceptions

With JNI, Java exceptions can be thrown, caught, printed, and rethrown just as they are inside a Java program. But it’s up to the programmer to call dedicated JNI functions to deal with exceptions. Here are the JNI functions for exception handling:

Among these, you can’t ignore ExceptionOccurred( ) and ExceptionClear( ). Most JNI functions can generate exceptions, and there is no language feature that you can use in place of a Java try block, so you must call ExceptionOccurred( ) after each JNI function call to see if an exception was thrown. If you detect an exception, you may choose to handle it (and possibly rethrow it). You must make certain, however, that the exception is eventually cleared. This can be done in your function using ExceptionClear( ) or in some other function if the exception is rethrown, but it must be done.

You must ensure that the exception is cleared, because otherwise the results will be unpredictable if you call a JNI function while an exception is pending. There are few JNI functions that are safe to call during an exception; among these, of course, are all the exception handling functions.

JNI and threading

Since Java is a multithreaded language, several threads can call a native method concurrently. (The native method might be suspended in the middle of its operation when a second thread calls it.) It’s entirely up to the programmer to guarantee that the native call is thread-safe, i.e. it does not modify shared data in an unmonitored way. Basically, you have two options: declare the native method as synchronized or implement some other strategy within the native method to ensure correct, concurrent data manipulation.

Also, you should never pass the JNIEnv pointer across threads, since the internal structure it points to is allocated on a per-thread basis and contains information that makes sense only in that particular thread.

Using a pre-existing code base

The easiest way to implement JNI native methods is to start writing native method prototypes in a Java class, compile that class, and run the .class file through javah. But what if you have a large, pre-existing code base that you want to call from Java? Renaming all the functions in your DLLs to match the JNI name mangling convention is not a viable solution. The best approach is to write a wrapper DLL “outside” your original code base. The Java code calls functions in this new DLL, which in turn calls your original DLL functions. This solution is not just a work-around; in most cases you must do this anyway because you must call JNI functions on the object references before you can use them.

The Microsoft way

At the time of this writing, Microsoft does not support JNI, but provides proprietary support to call non-Java code. This support is built into the compiler, the Microsoft JVM, and external tools. The features described in this section will work only if your program was compiled using the Microsoft Java compiler and run on the Microsoft Java Virtual Machine. If you plan to distribute your application on the Internet, or if your Intranet is built on different platforms, this can be a serious issue.

The Microsoft interface to Win32 code provides three ways to connect to Win32:

  1. J/Direct: A way to easily call Win32 DLL functions, with some limitations.
  2. Raw Native Interface (RNI): You can call Win32 DLL functions, but you must then handle garbage collection.
  3. Java/COM integration: You can expose or call COM services directly from Java.

I’ll cover all three techniques in the following sections.

At the time of writing, these features were tested on the Microsoft SDK for Java 2.0 beta 2, which was downloaded (with a painful process they call “Active Setup”) from the Microsoft Web site. The Java SDK is a set of command-line tools, but the compilation engine can be easily plugged into the Developer Studio environment, allowing you to use Visual J++ 1.1 to compile Java 1.1 code.

J/Direct

J/Direct is the simplest way to call functions in a Win32 DLL. It was designed primarily to interface with the Win32 API, but you can use it to call any other APIs. The ease of use of this feature is counterbalanced by some limitations and reduced performance (compared to RNI). But J/Direct has distinct advantages. First, there is no need to write additional non-Java code, except the code in the DLL you want to call. In other words, you do not need a wrapper or proxy/stub DLL. Second, function arguments are automatically converted to and from standard data types. (If you must pass user-defined data types, J/Direct might not be the way to go.) Third, it’s simple and straightforward, as the example below shows. In just a few lines, this example calls the Win32 API function MessageBox( ), which pops up a little modal window with a title, a message, an optional icon, and a few buttons.

public class ShowMsgBox {
  public static void main(String args[]) 
  throws UnsatisfiedLinkError   {
    MessageBox(0,
      "Created by the MessageBox() Win32 func",
      "Thinking in Java", 0);
  }
  /** @dll.import("USER32") */
  private static native int 
  MessageBox(int hwndOwner, String text,
    String title, int fuStyle);
}

Amazingly, this code is all you need to call a function in a Win32 DLL using J/Direct. The key is the @dll.import directive before the MessageBox( ) declaration, at the bottom of the example code. It looks like a comment, but it’s not: it tells the compiler that the function below the directive is implemented in the USER32 DLL, and should be called accordingly. All you must do is supply a prototype that matches the function implementation in the DLL and call the function. But instead of typing in the Java version of each Win32 API function that you need, a Microsoft Java package does this for you (I’ll describe this shortly). For this example to work, the function must be exported by name by the DLL, but the @dll.import directive can be used to link by ordinal as well, i.e., you can specify the entry position of the function in the DLL. I’ll cover the features of the @dll.import directive later.

An important issue in the process of linking with non-Java code is the automatic marshaling of the function parameters. As you can see, the Java declaration of MessageBox( ) takes two String arguments, but the original C implementation takes two char pointers. The compiler automatically converts the standard data types for you, following the rules described in a later section.

Finally, you might have noticed the UnsatisfiedLinkError exception in the declaration of main( ). This exception occurs when the linker is unable to resolve the symbol for the non-Java function at run-time. This happens for a number of reasons: the .dll file was not found, it is not a valid DLL, or J/Direct is not supported by your virtual machine. For the DLL to be found, it must be in the Windows or Windows\System directory, in one of the directories listed in your PATH environment variable, or in the directory where the .class file is located. J/Direct is supported in the Microsoft Java compiler version 1.02.4213 or above, and in the Microsoft JVM version 4.79.2164 or above. To get the compiler version number, run JVC from the command line with no parameters. To get the JVM version number, locate the icon for msjava.dll, and using the context menu look at its properties.

The @dll.import directive

The @dll.import directive, your one and only way to J/Direct, is quite flexible. It has a number of modifiers that you can use to customize the way you link to the non-Java code. It can also be applied to some methods within a class or to a whole class, meaning that all of the methods you declare in that class are implemented in the same DLL. Let’s look at these features.

Aliasing and linking by ordinal

For the @dll.import directive to work as shown above, the function in the DLL must be exported by name. However, you might want to use a different name than the original one in the DLL (aliasing), or the function might be exported by number (i.e. by ordinal) instead of by name. The example below declares FinestraDiMessaggio( ) (the Italian equivalent of “MessageBox”) as an alias to MessageBox( ). As you can see, the syntax is pretty simple.

public class Aliasing {
  public static void main(String args[]) 
  throws UnsatisfiedLinkError   {
    FinestraDiMessaggio(0,
      "Created by the MessageBox() Win32 func",
      "Thinking in Java", 0);
  }
  /** @dll.import("USER32", 
  entrypoint="MessageBox") */
  private static native int 
  FinestraDiMessaggio(int hwndOwner, String text,
    String title, int fuStyle);
}

The next example shows how to link to a function in a DLL that is not exported by name, but by its position inside of the DLL. The example assumes that there is a DLL named MYMATH somewhere along your path, and that this DLL contains at position 3 a function that takes two integers and gives you back the sum.

public class ByOrdinal {
  public static void main(String args[]) 
  throws UnsatisfiedLinkError {
    int j=3, k=9;
    System.out.println("Result of DLL function:"
      + Add(j,k));
  }
  /** @dll.import("MYMATH", entrypoint = "#3") */
  private static native int Add(int op1,int op2);
}

You can see the only difference is the form of the entrypoint argument.

Applying @dll.import to the entire class

The @dll.import directive can be applied to an entire class, meaning that all of the methods in that class are implemented in the same DLL and with the same linkage attributes. The directive is not inherited by subclasses; for this reason, and since functions in a DLL are by nature static functions, a better design approach is to encapsulate the API functions in a separate class, as shown here:

/** @dll.import("USER32") */
class MyUser32Access {
  public static native int 
  MessageBox(int hwndOwner, String text,
    String title, int fuStyle);
  public native static boolean 
  MessageBeep(int uType);
}

public class WholeClass {
  public static void main(String args[]) 
  throws UnsatisfiedLinkError {
    MyUser32Access.MessageBeep(4);
    MyUser32Access.MessageBox(0,
      "Created by the MessageBox() Win32 func",
      "Thinking in Java", 0);
  }
}

Since the MessageBeep( ) and MessageBox( ) functions are now declared as static in a different class, you must call them specifying their scope. You might think that you must use the approach above to map all of the Win32 API (functions, constants, and data types) to Java classes. Fortunately, you don’t have to.

The com.ms.win32 package

The Win32 API is fairly big – on the order of a thousand functions, constants, and data types. Of course, you do not want to write the Java equivalent of every single Win32 API function. Microsoft took care of this, distributing a Java package that maps the Win32 API to Java classes using J/Direct. This package, named com.ms.win32, is installed in your classpath during the installation of the Java SDK 2.0 if you select it in the setup options. The package is made up of large number of Java classes that reproduce the constants, data structures, and functions of the Win32 API. The three richest classes are User32.class, Kernel32.class, and Gdi32.class. These contain the core of the Win32 API. To use them, just import them in your Java code. The ShowMsgBox example above can be rewritten using com.ms.win32 as follows (I also took care of the UnsatisfiedLinkError in a more civilized way):

import com.ms.win32.*;

public class UseWin32Package {
  public static void main(String args[]) {
    try {
      User32.MessageBeep(
        winm.MB_ICONEXCLAMATION);
      User32.MessageBox(0,
        "Created by the MessageBox() Win32 func",
        "Thinking in Java",
        winm.MB_OKCANCEL |
        winm.MB_ICONEXCLAMATION);
    } catch(UnsatisfiedLinkError e) {
      System.out.println("Can’t link Win32 API");
      System.out.println(e);
    }
  }
}

The package is imported in the first line. The MessageBeep( ) and MessageBox( ) functions can now be called with no other declarations. In MessageBeep( ) you can see that importing the package has also declared the Win32 constants. These constants are defined in a number of Java interfaces, all named winx (x is the first letter of the constant you want to use).

At the time of this writing, the classes in the com.ms.win32 package are still under development, but usable nonetheless.

Marshaling

Marshaling means converting a function argument from its native binary representation into some language-independent format, and then converting this generic representation into a binary format that is appropriate to the called function. In the example above, we called the MessageBox( ) function and passed it a couple of Strings. MessageBox( ) is a C function, and the binary layout of Java Strings is not the same as C strings, but the arguments are nonetheless correctly passed. That’s because J/Direct takes care of converting a Java String into a C string before calling the C code. This happens with all standard Java types. Below is a table of the implicit conversions for simple data types:

Java

C

byte

BYTE or CHAR

short

SHORT or WORD

int

INT, UINT, LONG, ULONG, or DWORD

char

TCHAR

long

__int64

float

Float

double

Double

boolean

BOOL

String

LPCTSTR (Allowed as return value only in ole mode)

byte[]

BYTE *

short[]

WORD *

char[]

TCHAR *

int[]

DWORD *

The list continues, but this gives you the idea. In most cases, you do not need to worry about converting to and from simple data types, but things are different when you must pass arguments of user-defined data types. For example, you might need to pass the address of a structured, user-defined data type, or you might need to pass a pointer to a raw memory area. For these situations, there are special compiler directives to mark a Java class so that it can be passed as a pointer to a structure (the @dll.struct directive). For details on the use of these keywords, please refer to the product documentation.

Writing callback functions

Some Win32 API functions require a function pointer as one of the parameters. The Windows API function may then call the argument function, possibly at a later time when some event occurs. This technique is called a callback function. Examples include window procedures and the callbacks you set up during a print operation (you give the print spooler the address of your callback function so it can update the status and possibly interrupt printing).

Another example is the EnumWindows( ) API function that enumerates all top-level windows currently present in the system. EnumWindows( ) takes a function pointer, then traverses a list maintained internally by Windows. For every window in the list, it calls the callback function, passing the window handle as an argument to the callback.

To do the same thing in Java, you must use the Callback class in the com.ms.dll package. You inherit from Callback and override callback( ). This method will accept only int parameters and will return int or void. The method signature and implementation depends on the Windows API function that’s using this callback.

Now all we need to do is create an instance of this Callback-derived class and pass it as the function pointer argument to the API function. J/Direct will take care of the rest.

The example below calls the EnumWindows( ) Win32 API; the callback( ) method in the EnumWindowsProc class gets the window handle for each top-level window, obtains the caption text, and prints it to the console window.

import com.ms.dll.*;
import com.ms.win32.*;

class EnumWindowsProc extends Callback {
  public boolean callback(int hwnd, int lparam) {
    StringBuffer text = new StringBuffer(50);
    User32.GetWindowText(
      hwnd, text, text.capacity()+1);
    if(text.length() != 0)
      System.out.println(text);
    return true;  // to continue enumeration.
  }
}

public class ShowCallback {
  public static void main(String args[])
  throws InterruptedException {
    boolean ok = User32.EnumWindows(
      new EnumWindowsProc(), 0);
    if(!ok)
      System.err.println("EnumWindows failed.");
    Thread.currentThread().sleep(3000);
  }
}

The call to sleep( ) allows the windows procedure to complete before main( ) exits.

Other J/Direct features

There are two more J/Direct features you can get using modifiers in the @dll.import directive. The first is simplified access to OLE functions, and the second is the selection of the ANSI versus Unicode version of API functions. Here is a short description of the two.

By convention, all OLE functions return a value of type HRESULT, which is a structured integer value defined by COM. If you program at the COM level and you want something different returned from an OLE function, you must pass it a pointer to a memory area that the function will fill with data. But in Java we don’t have pointers; also, this style is not exactly elegant. With J/Direct, you can easily call OLE functions using the ole modifier in the @dll.import directive. A native method marked as an ole function is automatically translated from a Java-style method signature, which is where you decide the return type, into a COM-style function.

The second feature selects between ANSI and Unicode string handling. Most Win32 API functions that handle strings come in two versions. For example, if you look at the symbols exported by the USER32 DLL, you will not find a MessageBox( ) function, but instead MessageBoxA( ) and MessageBoxW( ) functions, which are the ANSI and Unicode version, respectively. If you do not specify which version you want to call in the @dll.import directive, the JVM will try to figure it out. But this operation takes some time during program execution time that you can save with the ansi, unicode, or auto modifiers.

For a more detailed discussion of these features, consult the Microsoft documentation.

Raw Native Interface (RNI)

Compared to J/Direct, RNI is a fairly complex interface to non-Java code, but it’s much more powerful. RNI is closer to the JVM than J/Direct, and this lets you write much more efficient code, manipulate Java objects in your native methods, and in general gives you a much higher degree of integration with the JVM internal operations.

RNI is conceptually similar to Sun’s JNI. Because of this, and because the product is not yet completed, I’ll just point out the major differences. For further information, please refer to Microsoft’s documentation.

There are several notable differences between JNI and RNI. Below is the C header file generated by msjavah, the Microsoft equivalent of Sun’s javah, applied to the ShowMsgBox Java class file used previously for the JNI example.

/*  DO NOT EDIT - 
automatically generated by msjavah  */
#include <native.h>
#pragma warning(disable:4510)
#pragma warning(disable:4512)
#pragma warning(disable:4610)

struct Classjava_lang_String;
#define Hjava_lang_String Classjava_lang_String

/*  Header for class ShowMsgBox  */

#ifndef _Included_ShowMsgBox
#define _Included_ShowMsgBox

#define HShowMsgBox ClassShowMsgBox
typedef struct ClassShowMsgBox {
#include <pshpack4.h>
  long MSReserved;
#include <poppack.h>
} ClassShowMsgBox;

#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void __cdecl 
ShowMsgBox_ShowMessage (struct HShowMsgBox *, 
  struct Hjava_lang_String *);
#ifdef __cplusplus
}
#endif

#endif  /* _Included_ShowMsgBox */

#pragma warning(default:4510)
#pragma warning(default:4512)
#pragma warning(default:4610)

Apart from being less readable, there are more technical issues disguised in the code, which we’ll examine.

In RNI, the native method programmer knows the binary layout of the objects. This allows you to directly access the information you want; you don’t need to get a field or method identifier as in JNI. But since not all virtual machines necessarily use the same binary layout for their objects, the native method above is guaranteed to run only under the Microsoft JVM.

In JNI, the JNIEnv argument gives access to a large number of functions to interact with the JVM. In RNI, the functions for controlling JVM operations are directly available. Some of them, like the one for handling exceptions, are similar to their JNI counterparts, but most of the RNI functions have different names and purposes from those in JNI.

One of the most remarkable differences between JNI and RNI is the garbage collection model. In JNI, the GC basically follows the same rules during native method execution that it follows for the Java code execution. In RNI, the programmer is responsible for starting and stopping the Garbage Collector during native method activity. By default, the GC is disabled upon entering the native method; doing so, the programmer can assume that the objects being used will not be garbage collected during that time. But if the native method, let’s say, is going to take a long time, the programmer is free to enable the GC, calling the GCEnable( ) RNI function.

There is also something similar to the global handles features – something the programmer can use to be sure that specific objects will not be garbage collected when the CG is enabled. The concept is similar but the name is different: in RNI these guys are called GCFrames.

RNI Summary

The fact that RNI is tightly integrated with the Microsoft JVM is both its strength and its weakness. RNI is more complex than JNI, but it also gives you a high degree of control of the internal activities of the JVM, including garbage collection. Also, it is clearly designed for speed, adopting compromises and techniques that C programmers are familiar with. But it’s not suitable for JVMs other than Microsoft’s.

Java/COM integration

COM (formerly known as OLE) is the Microsoft Component Object Model, the foundation of all ActiveX technologies. These include ActiveX Controls, Automation, and ActiveX Documents. But COM is much more; it’s a specification (and a partial implementation) for developing component objects that can interoperate using dedicated features of the operating system. In practice, all of the new software developed for Win32 systems has some relationship with COM – the operating system exposes some of its features via COM objects. Third-party components can be COM, and you can create and register your own COM components. In one way or another, if you want to write Win32 code, you’ll have to deal with COM. Here, I’ll just recap the fundamentals of COM programming, and I’ll assume that you are familiar with the concept of a COM server (any COM object that can expose services to COM clients) and a COM client (a COM object that uses the services provided by a COM server). This section kept things simple; the tools are actually much more powerful, and you can use them in a more sophisticated way. But this requires a deep knowledge of COM, which is beyond the scope of this appendix. If you’re interested in this powerful but platform-dependent feature, you should investigate COM and the Microsoft documentation on Java/COM integration. For more information, Dale Rogerson’s “Inside COM” (Microsoft Press, 1997) is an excellent book.

Since COM is the architectural heart of all the new Win32 applications, being able to use, or to expose, COM services from Java code can be important. The Java/COM integration is no doubt one of the most interesting features of the Microsoft Java compiler and virtual machine. Java and COM are so similar in their models that the integration is conceptually straightforward and technically seamless – there’s almost no special code to write in order to access COM. Most the details are handled by the compiler and/or by the virtual machine. The effect is that the COM objects are seen as ordinary Java objects by the Java programmer, and COM clients can use COM servers implemented in Java just like any other COM server. Again, I use the generic term COM, but by extension this means that you can implement an ActiveX Automation server in Java, or you can use an ActiveX Control in your Java programs.

The most notable similarities between Java and COM revolve around the relationship between COM interfaces and the Java interface keyword. This is a near-perfect match because:

This tight mapping between Java and COM not only allows the Java programmer to easily access COM features, but it also makes Java an interesting language for writing COM code. COM is language-independent, but the de facto languages for COM development are C++ and Visual Basic. Compared to Java, C++ is much more powerful for COM development and generates much more efficient code, but it’s hard to use. Visual Basic is much easier than Java, but it’s too far from the underlying operating system, and its object model does not map very well to COM. Java is an excellent compromise between the two.

Let’s take a look at some of the keys points of COM development that you need to know to write Java/COM clients and servers.

COM Fundamentals

COM is a binary specification for implementing interoperable objects. For example, COM describes the binary layout an object should have to be able to call services in another COM object. Since it’s a description of a binary layout, COM objects can be implemented in any language that’s able to produce such a layout. Usually the programmer is freed from these low level details, since the compiler takes care of generating the correct layout. For example, if you program in C++, most compilers generate a virtual function table that is COM-compliant. With languages that do not produce executable code, such as VB and Java, the runtime takes care of hooking into COM.

The COM Library also supplies a few basic functions, such as the ones for creating an object or locating a COM class registered in your system.

The main goals of a component object model are:

The first point is exactly what object-oriented programming is about: you have a client object that makes requests to a server object. In this case, the terms “client” and “server” are used in a generic way, and not to refer to some particular hardware configuration. With any object-oriented language, the first goal is easy to achieve if your application is a monolithic piece of code that implements both the server object code and the client object code. If you make changes to the way client and the server objects interface with each other, you simply compile and link again. When you restart your application, it uses a new version of the components.

The situation is completely different when your application is made up of component objects that are not under your control – you don’t control their source code and they can evolve separately from your application. This is exactly the case, for example, when you use a third-party ActiveX Control in your application. The control is installed in your system, and your application is able, at runtime, to locate the server code, activate the object, link to it, and use it. Later, you can install a newer version of the control, and your application should still be able to run; in the worst case, it should gracefully report an error condition, such as “Control not found,” without hanging up.

In these scenarios, your components are implemented in separate executable code files: DLLs or EXEs. If the server object is implemented in a separate executable code file, you need a standard, operating system supplied method to activate these objects. Of course, in your code you do not want to use the physical name and location of the DLL or EXE, because these might change; you want some identifier maintained by the operating system. Also, your application needs a description of the services exposed by the server. This is exactly what I’ll cover in the next two sections.

GUIDs and the Registry

COM uses structured integer numbers, 128 bits long, to unequivocally identify COM entities registered in the system. These numbers, called GUIDs (Globally Unique IDentifiers) can be generated by specific utilities, and are guaranteed to be unique “in space and in time,” to quote Kraig Brockschmidt. In space, because the number is generator reads the id of your network card, and in time because the system date and time are used as well. A GUID can be used to identify a COM class (in which case it’s called a CLSID) or a COM interface (IID). The names are different but the concept and the binary structure are the same. GUIDs are also used in other situations that I will not cover here.

GUIDs, along with their associated information, are stored in the Windows Registry, or Registration Database. It’s a hierarchical database, built into the operating system, which holds a great amount of information about the hardware and software configuration of your system. For COM, the Registry keeps track of the components installed in your system, such as their CLSIDs, the name and location of the executable file that implement them, and a lot of other details. One of these details is the ProgID of the component; a ProgID is conceptually similar to a GUID in the sense that it identifies a COM component. The difference is that a GUID is a binary, algorithmically-generated value, whereas a ProgID is a programmer-defined string value. A ProgID is associated with a CLSID.

A COM component is said to be registered in the system when at least its CLSID and its executable file location are present in the Registry (the ProgID is usually present as well). Registering and using COM components is exactly what we’ll do in the following examples.

One of the effects of the Registry is as a decoupling layer between the client and server objects. The client activates the server using some information that is stored in the Registry; one piece of information is the physical location of the server executables. If the location changes, the information in the Registry is updated accordingly, but this is transparent to the client, which just uses ProgIDs or CLSIDs. In other words, the Registry allows for location transparency of the server code. With the introduction of DCOM (Distributed COM), a server that was running on a local machine can even be moved to a remote machine on the network, without the client even noticing it (well, almost...).

Type Libraries

Because of COM’s dynamic linking and the independent evolution of client and server code, the client always needs to dynamically detect the services that are exposed by the server. These services are described in a binary, language-independent way (as interfaces and method signatures) in the type library. This can be a separate file (usually with the .TLB extension), or a Win32 resource linked into the executable. At runtime, the client uses the information in the type library to call functions in the server.

You can generate a type library by writing a Microsoft Interface Definition Language (MIDL) source file and compiling it with the MIDL compiler to generate a .TLB file. MIDL is a language that describes COM classes, interfaces, and methods. It resembles the OMG/CORBA IDL in name, syntax, and purpose. The Java programmer has no need to use MIDL, though. A different Microsoft tool, described later, reads a Java class file and generates a type library.

Function return codes in COM: HRESULT

COM functions exposed by a server return a value of the predefined type HRESULT. An HRESULT is an integer containing three fields. This allows for multiple failure and success codes, along with additional information. Because a COM function returns an HRESULT, you cannot use the return value to hand back ordinary data from the function call. If you must return data, you pass a pointer to a memory area that the function will fill. This is known as an out parameter. You don’t need to worry about this as a Java/COM programmer since the virtual machine takes care of it for you. This is described in the following sections.

MS Java/COM Integration

The Microsoft Java compiler, Virtual Machine, and tools make life a lot easier for the Java/COM programmer than it is for the C++/COM programmer. The compiler has special directives and packages for treating Java classes as COM classes, but in most cases, you’ll just rely on the Microsoft JVM support for COM, and on a couple of external tools.

The Microsoft Java Virtual Machine acts as a bridge between COM and Java objects. If you create a Java object as a COM server, your object will still be running inside the JVM. The Microsoft JVM is implemented as a DLL, which exposes COM interfaces to the operating system. Internally, the JVM maps function calls to these COM interfaces to method calls in your Java objects. Of course, the JVM must know which Java class file corresponds to the server executable; it can discover this information because you previously registered the class file in the Windows Registry using Javareg, a utility in the Microsoft Java SDK. Javareg reads a Java class file, generates a corresponding type library and a GUID, and registers the class in the system. Javareg can be used to register remote servers as well, for example, servers that run on a different physical machine.

If you want to write a Java/COM client, you must go through a different process. A Java/COM client is Java code that wants to activate and use one of the COM servers registered on your system. Again, the virtual machine interfaces with the COM server and exposes its services as methods in a Java class. Another Microsoft tool, jactivex, reads a type library and generates Java source files that contain special compiler directives. The generated source files are part of a package named after the type library you specified. The next step is to import that package in your COM client Java source files.

Let’s look at a couple of examples.

Developing COM servers in Java

This section shows the process you will apply to the development of ActiveX Controls, Automation Servers, or any other COM-compliant server. The following example implements a simple Automation server that adds integer numbers. You set the value of the addend with the setAddend( ) method, and each time you call the sum( ) method the addend is added to the current result. You retrieve the result with getResult( ) and reset the values with clear( ). The Java class that implements this behavior is straightforward:

public class Adder {
  private int addend;
  private int result;
  public void setAddend(int a) { addend = a; }
  public int getAddend() { return addend; }
  public int getResult() { return result; }
  public void sum() { result += addend;  }
  public void clear() {
    result = 0;
    addend = 0;
  }
}

To use this Java class as a COM object, the Javareg tool is applied to the compiled Adder.class file. This tool has a number of options; in this case we specify the Java class file name (“Adder”), the ProgID we want to put in the Registry for this server (“JavaAdder.Adder.1”), and the name we want for the type library that will be generated (”JavaAdder.tlb”). Since no CLSID is given, Javareg will generate one; if we call Javareg again on the same server, the existing CLSID will be used.

javareg /register
/class:Adder /progid:JavaAdder.Adder.1
/typelib:JavaAdder.tlb

Javareg also registers the new server in the Windows Registry. At this point, you must remember to copy your Adder.class file into the Windows\Java\trustlib directory. For security reasons, related mostly to the use of COM services by applets, your COM server will be activated only if it is installed in the trustlib directory.

You now have a new Automation server installed on your system. To test it, you need an Automation controller, and “the” Automation Controller is Visual Basic (VB). Below, you can see a few lines of VB code. On the VB form, I put a text box to input the value of the addend, a label to show the result, and two push buttons to invoke the sum( ) and clear( ) methods. At the beginning, an object variable named Adder is declared. In the Form_Load subroutine, executed when the form is first displayed, a new instance of the Adder automation server is instantiated and the text fields on the form are initialized. When the user presses the “Sum” or “Clear” buttons, appropriate methods in the server are invoked.

Dim Adder As Object

Private Sub Form_Load()
    Set Adder = CreateObject("JavaAdder.Adder.1")
    Addend.Text = Adder.getAddend
    Result.Caption = Adder.getResult
End Sub

Private Sub SumBtn_Click()
    Adder.setAddend (Addend.Text)
    Adder.Sum
    Result.Caption = Adder.getResult
End Sub

Private Sub ClearBtn_Click()
    Adder.Clear
    Addend.Text = Adder.getAddend
    Result.Caption = Adder.getResult
End Sub

Note that this code has no knowledge that the server was implemented in Java.

When you run this program and the CreateObject( ) function is called, the Windows Registry is searched for the specified ProgID. Among the information related to the ProgID is the name of the Java class file, so in response the Java Virtual Machine is started, and the Java object instantiated inside the JVM. From then on, the JVM takes care of the interaction between the client and server code.

Developing COM clients in Java

Now let’s jump to the other side and develop a COM client in Java. This program will call services in a COM server that’s installed on your system. The example is a client for the server we implemented in the previous example. While the code will look familiar to a Java programmer, what happens behind the scenes is quite unusual. This example uses a server that happens to be written in Java but applies to any ActiveX Control, ActiveX Automation server, or ActiveX component installed in your system for which you have a type library.

First, the Jactivex tool is applied to the server’s type library. Jactivex has a number of options and switches, but in its basic form it reads a type library and generates Java source files, which it stores in your windows/Java/trustlib directory. In the example line below, it is applied to the type library that was generated for out COM Automation server.

jactivex /javatlb JavaAdder.tlb

If, after Jactivex has finished, you take a look at your windows/Java/trustlib directory, you’ll find a new subdirectory called javaadder that contains the source files for a new package. This is the Java equivalent of the type library. These files use compiler directives specific to the Microsoft compiler: the @com directives. The reason jactivex generated more than one file is that COM uses more than one entity to describe a COM server (and also because I did not fine-tune the use of MIDL files and the Java/COM tools).

The file named Adder.java is the equivalent of a coclass directive in a MIDL file: it’s the declaration of a COM class. The other files are the Java equivalent of the COM interfaces exposed by the server. These interfaces, such as Adder_DispatchDefault.java, are dispatch interfaces, part of the mechanism of interaction between an Automation controller and an Automation server. The Java/COM integration feature also supports the implementation and use of dual interfaces. IDispatch and dual interfaces are beyond the scope of this appendix.

Below, you can see the client code. The first line just imports the package generated by jactivex. Then an instance of the COM Automation server is created and used, as if it was an ordinary Java class. Notice the typecast on the line where the COM object is instantiated. This is consistent with the COM object model. In COM, the programmer never has a reference to the whole object; instead, the programmer can only have references to one or more of the interfaces implemented in the class.

Instantiating a Java object of the Adder class tells COM to activate the server and to create an instance of this COM object. But then we must specify which interface we want to use, choosing among the ones implemented by the server. This is exactly what the typecast does. The interface used here is the default dispatch interface, the standard interface that an Automation controller uses to communicate with an Automation server (for details, see Inside COM, ibid.). Notice how simple it is to activate the server and select a COM interface:

import javaadder.*;

public class JavaClient {
  public static void main(String [] args) {
    Adder_DispatchDefault iAdder =
         (Adder_DispatchDefault) new Adder();
    iAdder.setAddend(3);
    iAdder.sum();
    iAdder.sum();
    iAdder.sum();
    System.out.println(iAdder.getResult());
  }
}

Now you can compile and run the code.

The com.ms.com package

The com.ms.com package defines a number of classes for COM development. It supports the use of GUIDs – the Variant and SafeArray Automation types – interfacing with ActiveX Controls at a deeper level and handling COM exceptions.

I cannot cover all of these topics here, but I want to point out something about COM exceptions. By convention, virtually all COM functions return an HRESULT value that tells you if the function invocation succeeded or not and why. But if you look at the Java method signature in our server and client code, there no HRESULT. Instead, we use the function return value to get data back from some functions. The virtual machine is translating Java-style function calls into COM-style function calls, even for the return parameter. But what happens inside the virtual machine if one of the functions you call in the server fails at the COM level? In this case, the JVM sees that the HRESULT value indicates a failure and generates a native Java exception of class com.ms.com.ComFailException. In this way, you can handle COM errors using Java exception handling instead of checking function return values.

To learn more about the classes in this package, please refer to the Microsoft documentation.

ActiveX/Beans integration

An interesting result of Java/COM integration is the ActiveX/Beans integration, by which a Java Bean can be hosted by an ActiveX container such as VB or any Microsoft Office product, and an ActiveX Control can be hosted by a Beans container such as Sun’s BeanBox. The Microsoft JVM takes care of the details. An ActiveX Control is just a COM server exposing predefined, required interfaces. A Bean is just a Java class that is compliant with a specific programming style. At the time this was written, however, the integration was not perfect. For example, the virtual machine is not able to map the JavaBeans event model to the COM event model. If you want to handle events from a Bean inside an ActiveX container, the Bean must intercept system events such as mouse actions via low-level techniques, not the standard JavaBeans delegation event model.

Apart from this, the ActiveX/Beans integration is extremely interesting. The concept and tools are exactly the same as discussed above, so please consult Microsoft’s documentation for more details.

A note about native methods and applets

Native methods face the security issue. When your Java code calls a native method, you pass control outside of the virtual machine “sandbox.” The native method has complete access to the operating system. Of course, this is exactly what you want if you write native methods, but it is not acceptable for applets, at least not implicitly. You don’t want an applet, downloaded from a remote Internet server, to be free to play with the file system and other critical areas of your machine unless you allow it to do so. To prevent this situation with J/Direct, RNI, and COM integration, only trusted Java code has permission to make native method calls. Different conditions must be met depending on the feature the applet is trying to use. For example, an applet that uses J/Direct must be digitally signed to indicate full trust. At the time of this writing, not all of these security mechanisms are implemented (in the Microsoft SDK for Java, beta 2), so keep an eye on the documentation as new versions become available.

CORBA

In large, distributed applications, your needs might not be satisfied by the preceding approaches. For example, you might want to interface with legacy datastores, or you might need services from a server object regardless of its physical location. These situations require some form of Remote Procedure Call (RPC), and possibly language independence. This is where CORBA can help.

CORBA is not a language feature; it’s an integration technology. It’s a specification that vendors can follow to implement CORBA-compliant integration products. CORBA is part of the OMG’s effort to define a standard framework for distributed, language-independent object interoperability.

CORBA supplies the ability to make remote procedure calls into Java objects and non-Java objects, and to interface with legacy systems in a location-transparent way. Java adds networking support and a nice object-oriented language for building graphical and non-graphical applications. The Java and OMG object model map nicely to each other; for example, both Java and CORBA implement the interface concept and a reference object model.

CORBA Fundamentals

The object interoperability specification developed by the OMG is commonly referred to as the Object Management Architecture (OMA). The OMA defines two components: the Core Object Model and the OMA Reference Architecture. The Core Object Model states the basic concepts of object, interface, operation, and so on. (CORBA is a refinement of the Core Object Model.) The OMA Reference Architecture defines an underlying infrastructure of services and mechanisms that allow objects to interoperate. The OMA Reference Architecture includes the Object Request Broker (ORB), Object Services (also known as CORBAservices), and common facilities.

The ORB is the communication bus by which objects can request services from other objects, regardless of their physical location. This means that what looks like a method call in the client code is actually a complex operation. First, a connection with the server object must exist, and to create a connection the ORB must know where the server implementation code resides. Once the connection is established, the method arguments must be marshaled, i.e. converted in a binary stream to be sent across a network. Other information that must be sent are the server machine name, the server process, and the identity of the server object inside that process. Finally, this information is sent through a low-level wire protocol, the information is decoded on the server side, and the call is executed. The ORB hides all of this complexity from the programmer and makes the operation almost as simple as calling a method on local object.

There is no specification for how an ORB Core should be implemented, but to provide a basic compatibility among different vendors’ ORBs, the OMG defines a set of services that are accessible through standard interfaces.

CORBA Interface Definition Language (IDL)

CORBA is designed for language transparency: a client object can call methods on a server object of different class, regardless of the language they are implemented with. Of course, the client object must know the names and signatures of methods that the server object exposes. This is where IDL comes in. The CORBA IDL is a language-neutral way to specify data types, attributes, operations, interfaces, and more. The IDL syntax is similar to the C++ or Java syntax. The following table shows the correspondence between some of the concepts common to three languages that can be specified through CORBA IDL:

CORBA IDL

Java

C++

Module

Package

Namespace

Interface

Interface

Pure abstract class

Method

Method

Member function

The inheritance concept is supported as well, using the colon operator as in C++. The programmer writes an IDL description of the attributes, methods, and interfaces that will be implemented and used by the server and clients. The IDL is then compiled by a vendor-provided IDL/Java compiler, which reads the IDL source and generates Java code.

The IDL compiler is an extremely useful tool: it doesn’t just generate a Java source equivalent of the IDL, it also generates the code that will be used to marshal method arguments and to make remote calls. This code, called the stub and skeleton code, is organized in multiple Java source files and is usually part of the same Java package.

The naming service

The naming service is one of the fundamental CORBA services. A CORBA object is accessed through a reference, a piece of information that’s not meaningful for the human reader. But references can be assigned programmer-defined, string names. This operation is known as stringifying the reference, and one of the OMA components, the Naming Service, is devoted to performing string-to-object and object-to-string conversion and mapping. Since the Naming Service acts as a telephone directory that both servers and clients can consult and manipulate, it runs as a separate process. Creating an object-to-string mapping is called binding an object, and removing the mapping is called unbinding. Getting an object reference passing a string is called resolving the name.

For example, on startup, a server application could create a server object, bind the object into the name service, and then wait for clients to make requests. A client first obtains a server object reference, resolving the string name, and then can make calls into the server using the reference.

Again, the Naming Service specification is part of CORBA, but the application that implements it is provided by the ORB vendor. The way you get access to the Naming Service functionality can vary from vendor to vendor.

An example

The code shown here will not be elaborate because different ORBs have different ways to access CORBA services, so examples are vendor specific. (The example below uses JavaIDL, a free product from Sun that comes with a light-weight ORB, a naming service, and a IDL-to-Java compiler.) In addition, since Java is young and still evolving, not all CORBA features are present in the various Java/CORBA products.

We want to implement a server, running on some machine, that can be queried for the exact time. We also want to implement a client that asks for the exact time. In this case we’ll be implementing both programs in Java, but we could also use two different languages (which often happens in real situations).

Writing the IDL source

The first step is to write an IDL description of the services provided. This is usually done by the server programmer, who is then free to implement the server in any language in which a CORBA IDL compiler exists. The IDL file is distributed to the client side programmer and becomes the bridge between languages.

The example below shows the IDL description of our exact time server:

module RemoteTime {
   interface ExactTime {
      string getTime();
   };
};

This is a declaration of the ExactTime interface inside the RemoteTime namespace. The interface is made up of one single method the gives back the current time in string format.

Creating stubs and skeletons

The second step is to compile the IDL to create the Java stub and skeleton code that we’ll use for implementing the client and the server. The tool that comes with the JavaIDL product is idltojava:

idltojava –fserver –fclient RemoteTime.idl

The two flags tell idltojava to generate code for both the stub and the skeleton. Idltojava generates a Java package named after the IDL module, RemoteTime, and the generated Java files are put in the RemoteTime subdirectory. _ExactTimeImplBase.java is the skeleton that we’ll use to implement the server object, and _ExactTimeStub.java will be used for the client. There are Java representations of the IDL interface in ExactTime.java and a couple of other support files used, for example, to facilitate access to the naming service operations.

Implementing the server and the client

Below you can see the code for the server side. The server object implementation is in the ExactTimeServer class. The RemoteTimeServer is the application that creates a server object, registers it with the ORB, gives a name to the object reference, and then sits quietly waiting for client requests.

import RemoteTime.*;

import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

import java.util.*;
import java.text.*;

// Server object implementation
class ExactTimeServer extends _ExactTimeImplBase{
  public String getTime(){
    return DateFormat.
        getTimeInstance(DateFormat.FULL).
          format(new Date(
              System.currentTimeMillis()));
  }
}

// Remote application implementation
public class RemoteTimeServer {
  public static void main(String args[])  {
    try {
      // ORB creation and initialization:
      ORB orb = ORB.init(args, null);
      // Create the server object and register it:
      ExactTimeServer timeServerObjRef = 
        new ExactTimeServer();
      orb.connect(timeServerObjRef);
      // Get the root naming context:
      org.omg.CORBA.Object objRef = 
        orb.resolve_initial_references(
          "NameService");
      NamingContext ncRef = 
        NamingContextHelper.narrow(objRef);
      // Assign a string name to the 
      // object reference (binding):
      NameComponent nc = 
        new NameComponent("ExactTime", "");
      NameComponent path[] = {nc};
      ncRef.rebind(path, timeServerObjRef);
      // Wait for client requests:
      java.lang.Object sync =
        new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    }
    catch (Exception e)  {
      System.out.println(
         "Remote Time server error: " + e);
      e.printStackTrace(System.out);
    }
  }
}

As you can see, implementing the server object is simple; it’s a regular Java class that inherits from the skeleton code generated by the IDL compiler. Things get a bit more complicated when it comes to interacting with the ORB and other CORBA services.

Some CORBA services

This is a short description of what the JavaIDL-related code is doing (primarily ignoring the part of the CORBA code that is vendor dependent). The first line in main( ) starts up the ORB, and of course, this is because our server object will need to interact with it. Right after the ORB initialization, a server object is created. Actually, the right term would be a transient servant object: an object that receives requests from clients, and whose lifetime is the same as the process that creates it. Once the transient servant object is created, it is registered with the ORB, which means that the ORB knows of its existence and can now forward requests to it.

Up to this point, all we have is timeServerObjRef, an object reference that is known only inside the current server process. The next step will be to assign a stringified name to this servant object; clients will use that name to locate the servant object. We accomplish this operation using the Naming Service. First, we need an object reference to the Naming Service; the call to resolve_initial_references( ) takes the stringified object reference of the Naming Service that is “NameService,” in JavaIDL, and returns an object reference. This is cast to a specific NamingContext reference using the narrow( ) method. We can use now the naming services.

To bind the servant object with a stringified object reference, we first create a NameComponent object, initialized with “ExactTime,” the name string we want to bind to the servant object. Then, using the rebind( ) method, the stringified reference is bound to the object reference. We use rebind( ) to assign a reference, even if it already exists, whereas bind( ) raises an exception if the reference already exists. A name is made up in CORBA by a sequence of NameContexts – that’s why we use an array to bind the name to the object reference.

The servant object is finally ready for use by clients. At this point, the server process enters a wait state. Again, this is because it is a transient servant, so its lifetime is confined to the server process. JavaIDL does not currently support persistent objects – objects that survive the execution of the process that creates them.

Now that we have an idea of what the server code is doing, let’s look at the client code:

import RemoteTime.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

public class RemoteTimeClient {
  public static void main(String args[]) {
    try {
      // ORB creation and initialization:
      ORB orb = ORB.init(args, null);
      // Get the root naming context:
      org.omg.CORBA.Object objRef = 
        orb.resolve_initial_references(
          "NameService");
      NamingContext ncRef = 
        NamingContextHelper.narrow(objRef);
      // Get (resolve) the stringified object 
      // reference for the time server:
      NameComponent nc = 
        new NameComponent("ExactTime", "");
      NameComponent path[] = {nc};
      ExactTime timeObjRef = 
        ExactTimeHelper.narrow(
          ncRef.resolve(path));
      // Make requests to the server object:
      String exactTime = timeObjRef.getTime();
      System.out.println(exactTime);
    } catch (Exception e) {
      System.out.println(
         "Remote Time server error: " + e);
      e.printStackTrace(System.out);
    }
  }
}

The first few lines do the same as they do in the server process: the ORB is initialized and a reference to the naming service server is resolved. Next, we need an object reference for the servant object, so we pass the stringified object reference to the resolve( ) method, and we cast the result into an ExactTime interface reference using the narrow( ) method. Finally, we call getTime( ).

Activating the name service process

Finally we have a server and a client application ready to interoperate. You’ve seen that both need the naming service to bind and resolve stringified object references. You must start the naming service process before running either the server or the client. In JavaIDL, the naming service is a Java application that comes with the product package, but it can be different with other products. The JavaIDL naming service runs inside an instance of the JVM and listens by default to network port 900.

Activating the server and the client

Now you are ready to start your server and client application (in this order, since our server is transient). If everything is set up correctly, what you’ll get is a single output line on the client console window, giving you the current time. Of course, this might be not very exciting by itself, but you should take one thing into account: even if they are on the same physical machine, the client and the server application are running inside different virtual machines and they can communicate via an underlying integration layer, the ORB and the Naming Service.

This is a simple example, designed to work without a network, but an ORB is usually configured for location transparency. When the server and the client are on different machines, the ORB can resolve remote stringified references using a component known as the Implementation Repository. Although the Implementation Repository is part of CORBA, there is almost no specification, so it differs from vendor to vendor.

As you can see, there is much more to CORBA than what has been covered here, but you should get the basic idea. If you want more information about CORBA, the place to start is the OMG Web site, at http://www.omg.org. There you’ll find documentation, white papers, proceedings, and references to other CORBA sources and products.

Java Applets and CORBA

Java applets can act as CORBA clients. This way, an applet can access remote information and services exposed as CORBA objects. But an applet can connect only with the server from which it was downloaded, so all the CORBA objects the applet interacts with must be on that server. This is the opposite of what CORBA tries to do: give you complete location transparency.

This is an issue of network security. If you’re on an Intranet, one solution is to loosen the security restrictions on the browser. Or, set up a firewall policy for connecting with external servers.

Some Java ORB products offer proprietary solutions to this problem. For example, some implement what is called HTTP Tunneling, while others have their special firewall features.

This is too complex a topic to be covered in an appendix, but it is definitely something you should be aware of.

CORBA vs. RMI

You saw that one of the main CORBA features is RPC support, which allows your local objects to call methods in remote objects. Of course, there already is a native Java feature that does exactly the same thing: RMI (see Chapter 15). While RMI makes RPC possible between Java objects, CORBA makes RPC possible between objects implemented in any language. It’s a big difference.

However, RMI can be used to call services on remote, non-Java code. All you need is some kind of wrapper Java object around the non-Java code on the server side. The wrapper object connects externally to Java clients via RMI, and internally connects to the non-Java code using one of the techniques shown above, such as JNI or J/Direct.

This approach requires you to write a kind of integration layer, which is exactly what CORBA does for you, but then you don’t need a third-party ORB.

Summary

What you’ve seen in this appendix are the most common techniques to call non-Java code from a Java application. Each technique has its pros and cons, but currently the major problem is that not all of these features are available on all JVMs, so a Java program that calls native methods on a specific platform might not work on a different platform with a different JVM.

Sun’s JNI is flexible, reasonably simple (although it requires a lot of control over the JVM internals), powerful, and it’s available on most JVMs, but not all. Microsoft, at the time of this writing, does not support JNI, but offers J/Direct, a simple way to call Win32 DLL functions, and RNI, which is designed for high-performance code but requires a good understanding of the JVM internals. Microsoft also offers its proprietary Java/COM integration feature, which is powerful and makes Java an interesting language for writing COM servers and clients. J/Direct, RNI, and Java/COM integration are supported only by the Microsoft compiler and JVM.

Finally, we took a look at CORBA, which allows your Java objects to talk to other objects regardless of their physical location and implementation language. CORBA is different from the techniques above because it is not integrated with the Java language, but instead uses third-party integration technology and requires that you buy a third-party ORB. CORBA is an interesting and general solution, but it might not be the best approach if you just want to make calls into the operating system.


[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:02/04/2000