One of the strengths of the Java platform is its object-oriented nature; one of the benefits of object-oriented languages is that they aid code reuse. But what if you're a Java programmer and you want to reuse code that wasn't written in the Java language? With Bridge2Java, an IBM alphaWorks technology, Java developers can integrate COM objects into their applications. William Phillips explains how it works.
Practically anyone who programs computers is familiar with the concept of objects. One of the major advantages of programming with objects is that a particularly useful object, like a string formatter, can be used by a number of different parts of an application. If you take this philosophy to its logical conclusion, there's no reason why applications themselves can't be thought of and used as objects by other applications. That's the kind of environment the Component Object Model, or COM, was designed to help create.
Software developers can use the framework of COM to quickly and easily transform stand-alone applications into working parts of larger applications. But some high-level languages, like Visual Basic and the Java language, don't interface easily with COM. A layer called an automation controller must intervene between COM objects and objects written in these languages. Since more and more large applications are being written in the Java language, there is a need for a tool that eases integration. Microsoft's take on the Java platform, Visual J++, has an automation controller built in, but users of standard Java do not have this benefit.
Bridge2Java solves this problem on Windows by allowing COM objects to be used in Java programs like any other Java object. In this article, I will give you a high-level overview of Bridge2Java; I'll also provide an example showing how you can put it to use. If you'd like to know more about COM, check out the links in the Resources section below.
Bridge over troubled waters
The process by which a COM-enabled program (also called a COM server) allows other programs (also called COM clients) to access its methods and properties is called automation. Every COM server that supports automation must expose a particular interface called IDispatch. By using the IDispatch interface and the Invoke() method, a COM client can call any function that the COM server wants to expose. The Invoke() method is what Bridge2Java uses to allow a Java program to communicate with the COM server; this method also returns values to the Java program. From the perspective of the Java programmer, Bridge2Java allows a COM object to be "seen" by Java code as just another Java object. Functions can be called from objects; data types are all Java data types; and even COM events are captured and sent to the Java program.
|
|
JNI: The keystone
The boundary between the Java world and the COM world is bridged using the Java Native Interface (JNI). Only two JNI calls are required: one to create the COM server and another to process the Invoke() method. Invoke() is called whenever a Java function call is made; it's really the heart of the entire Java-to-COM process. However, if joining Java and COM objects were as simple as passing a call from Java code to COM through Invoke(), then Bridge2Java would be a very simple program. The real work of Bridge2Java is done in converting Java variables into a variable type that COM understands. This variable is called a VARIANT and is a structure that contains all possible types as well as a value that indicates the actual type of the data stored. Most Java variables map neatly to VARIANTs -- an int, for example, maps easily to the VT_I4 type in the VARIANT. The real difficulty lies in mapping COM types like SAFEARRAYs and BYREF values to Java equivalents.
The process of converting the variables starts with a Java proxy call that creates and populates a custom-built Java version of a VARIANT, called a Jvariant. This Jvariant can contain all Java variable types plus a field that indicates the type into which the contained data should be converted on the COM side. SAFEARRAYS in Bridge2Java are passed in from Java code in the form of arrays of Jvariants. Bridge2Java then builds an array of the proper type on the COM side and populates it with the values of the input array. If the array (or any other value) is BYTEREF, then Bridge2Java must be able to replace a passed-in value with an updated value. It does this by using one of several JNI calls that allow updating Java array elements from the COM side.
Figure 1 shows an overview of how Bridge2Java passes Java calls to the COM server.
Figure 1. Bridge2Java overview

|
A sample application: Building the bridge
The first step in using Bridge2Java is converting the interfaces, methods, and properties of the COM server into Java proxy objects and methods. This is accomplished by the proxy generator (included with Bridge2Java) and requires the user to locate the typelib for the COM server. (A typelib is simply a convenience supplied by the COM server programmer; it provides an orderly listing of all of the interfaces, methods, and properties for the COM server.) The Bridge2Java proxy generator steps through the typelib and generates a Java object for every IDispatch interface it finds. Each Java object thus created contains all of the methods and properties from the IDispatch interface. Properties will be generated as get_ and set_ methods, such as get_Visible() and set_Visible(BOOL visible).
Our sample application will show how Java developers can use Bridge2Java and COM to invoke Lotus 1-2-3 functionality. Listing 1 shows a fragment from the proxy generator. This example class is for our application's IDispatch interface to Lotus 1-2-3. (The typelib for 1-2-3 is called L123TYP.TLB).
There are several important things to note in this example. One is the cslid listed at the top of the file; this is the unique identifier for the IDispatch interface and is the value COM uses when it looks in the registry to find the location of Lotus 1-2-3. When Bridge2Java starts 1-2-3, COM passes back a pointer to the IDispatch interface that will be used by subsequent Invoke() calls to communicate with this COM server.
Another important part of the Java-to-COM process is the creation and initialization of the Jvariant class that contains the passed-in values and types. The invoke_method() call usually creates several Jvariants, one for each parameter passed in. In cases where only a value is returned, the Jvariant is created under the covers and only the return value is passed back.
Bridge2Java also uses the hex value that exists in all of the invoke_method() calls. These values are called dispids and they uniquely identify each method in the _Application interface. Invoke() uses these values, along with the IDispatch interface pointer mentioned above and any VARIANTs required, to uniquely locate and call any method presented by this interface.
// Dispatch Proxies
package Lot123;
import com.ibm.bridge2java.*;
import java.util.Date;
public class Application extends Dispatch implements COMconstants
{
public static final String clsid = "{29130071-2EED-1069-BF5D-00DD011186B7}";
public Application()
{
super(clsid);
}
public Application(String clsidin)
{
super(clsidin);
}
public Application(int IDispatch)
{
super(IDispatch);
}
public Application(Object theObject)
{
super(theObject);
}
public Application(int canvasHWND, int nullval)
{
super(clsid, canvasHWND);
}
public Application(String clsidin, int canvasHWND, int nullval)
{
super(clsidin, canvasHWND);
}
public String ExtendedName(Object nametype)
{
Jvariant args[] = {new Jvariant(nametype,VT_VARIANT)};
return invoke_method(args, 0x1000300, DISPATCH_METHOD).StringVal();
}
public void Goto()
{
invoke_method_void(null, 0x1000304, DISPATCH_METHOD);
}
.
.
.
public Object get_Run()
{
return invoke_method(null, 0x100ea00, DISPATCH_PROPERTYGET).ObjectVal();
}
public void set_Run(Object value)
{
Jvariant args[] = {new Jvariant(value, VT_VARIANT)};
invoke_method_void(args, 0x100ea00, DISPATCH_PROPERTYPUT);
}
public String get_ProductVersion()
{
return invoke_method(null, 0x14c0211, DISPATCH_PROPERTYGET).StringVal();
}
public void set_ProductVersion(String value)
{
Jvariant args[] = {new Jvariant(value, VT_BSTR)};
invoke_method_void(args, 0x14c0211, DISPATCH_PROPERTYPUT);
}
}
|
After generating the proxies, the user can now write a Java application to talk to the COM server. Listing 2 shows a short program that will bring up 1-2-3, write a line to a cell, copy that line, wait one second, and then close 1-2-3 without saving. It's a simple example, but one that illustrates everything necessary to build much more complex programs. A number of examples are included with the Bridge2Java package, which show, among other things, how to capture events and paint a COM server onto a Java canvas.
Listing 2. Quick123.java
// Description: This is a Bridge2Java Lotus 1-2-3 demo. This demo brings up
// 1-2-3, writes "This is a test" in the first cell,
// copies the first cell to the cell below it, waits 1 second,
// then quits.
//
// Setup: None
//
// Author: Bill Phillips
//
import Lot123.*;
public class Quick123
{
public static void main(java.lang.String[] args) {
Application app;
Ranges ranges;
Document doc;
Range rangeA1, rangeA2;
try
{
// Set up the com environment.
com.ibm.bridge2java.OleEnvironment.Initialize();
// Create a new document (which starts 123).
doc = new Document();
// Get the application object.
app = new Application(doc.get_Parent());
// Make the application visible.
app.set_Visible(new Boolean("true"));
// Open the Range workbook.
doc = new Document(app.NewDocument());
// Get all of the ranges.
ranges = new Ranges(doc.get_Ranges());
// Get cell A1.
rangeA1 = new Range(ranges.Item(new String("A1")) );
// Set the contents of cell A1
rangeA1.set_Contents("This is a test");
// Get cell A2.
rangeA2 = new Range(ranges.Item(new String("A2")) );
// Copy the contents of cell A1 into cell A2
rangeA1.QuickCopy(rangeA2);
// Wait one second
Thread.sleep(1000);
// Quit 123 without saving
app.Quit("false");
} catch (com.ibm.bridge2java.ComException e)
{
System.out.println( "COM Exception:" );
System.out.println( Long.toHexString((e.getHResult())) );
System.out.println( e.getMessage() );
} catch (Exception e)
{
System.out.println("message: " + e.getMessage());
} finally
{
app = null;
com.ibm.bridge2java.OleEnvironment.UnInitialize();
}
}
}
|
|
Wrapup
COM provides an architecture that allows programs to be used at a truly component level without regard for their version, the language they are written in, or even the location where they're running. The Java platform provides a language that is easy for the casual user to learn and work with. Bridge2Java crosses the gap between the complex yet powerful world of COM and the simplicity of the Java language.
Bridge2Java is currently an IBM alphaWorks technology, which means you can download it and evaluate it, sharing your thoughts with the development team. You can also license it for use in your own products. Click on the link to the Bridge2Java page on alphaWorks in Resources below.
discuss this topic to forum
