Bruce Eckel's Thinking in Java Contents | Prev | Next

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.

Contents | Prev | Next