Skip to main content

Integrate COM and Java components

Achieve interoperability with the Development Tool for Java-COM Bridge

Cheng-Yee Lin (clin@us.ibm.com), Development Manager, IBM Rational Software
Cheng-Yee Lin is a development manager for the IBM Rational modeling products. He has been involved in several projects related to compilers, UML, and API-based integrations. Mr. Lin holds M.S. and M.Ph. degrees in computer science from Yale University.
Thomas Houser (tmhouser@us.ibm.com), Software Engineer, IBM Rational Software
Tom Houser is the original developer of the Rational Java/COM Bridge technology. Prior to joining IBM, he worked in the software tools development industry for 24 years, working on compilers, debuggers, and modeling tools. Mr. Houser holds a B.S. in computer science from the University Wisconsin at Madison.
Peter Parapounsky (pjparapo@us.ibm.com), Software Engineer, IBM Rational Software
Peter Parapounsky is a software engineer at Rational Software, IBM Software Group. For the past five years, he's been working on different aspects of the Rational XDE product. Mr. Parapounsky holds an M.S. in computer science from the Technical University of Sofia, Bulgaria.

Summary:  Interoperability issues have long made integration of Microsoft® Component Object Model (COM) and Java™ components a daunting task. The Development Tool for Java-COM Bridge, available from IBM alphaWorks, simplifies the job and also provides an evolutionary approach to migrating applications from COM to the Java platform. IBM Rational's Cheng-Yee Lin, Thomas Houser, and Peter Parapounsky, creators of the bridging technology, explain its basics and present a sample application that leverages its capabilities.

Date:  16 Nov 2004
Level:  Introductory
Activity:  1126 views
Comments:  

As an organization's requirements and complexity grow, it might need to combine applications and components that have disparate underlying implementations into an integrated solution. Java technology and COM technology have both played major roles in the development of large-scale applications and components on Windows platforms, yet interoperability (or bridging) between the two technologies remains a largely unresolved issue in the integration world. A few tools have emerged in recent years that enable lightweight interactions between Java components and COM components. However, some of these tools have strictly limited applicability, such as those that support bridging only from Java components to COM (that is, calling a COM server's method from Java code). And bridging tools that are designed for generality impose a high performance overhead for intensive interactions.

This article introduces a bridging technology that balances performance with applicability. The IBM Rational Java-COM Bridge (RJCB) not only supports bridging from Java components to COM and from COM to Java components, but it also produces reasonable performance in intensive interactions between components through the bridges. The tooling for building RJCB bridges -- the Development Tool for Java-COM Bridge (DTJCB) -- integrates with the open source Eclipse IDE (see the sidebar, DTJCB as an extension to Eclipse), making it easy to create and use the RJCB bridges in a single Java environment. You can also use Microsoft tools for bridging from COM to Java components with bridges you create with DTJCB.

DTJCB also gives you a gradual migration path for converting large-scale COM applications into Java technology. Instead of making you revamp an entire application overnight, it lets you perform the migration component-by-component and keep the application available for use with minimal disruption.

Understanding RJCB technology

The RJCB technology uses the Java Native Interface (JNI) framework to bridge Java code and COM code. JNI lets you call native code in the Java language, and vice versa. You can declare a method in the Java language and define the method body in C or C++. Conversely, you can call Java methods in C or C++ code.

Figure 1 illustrates the structure of an RJCB bridge.

DTJCB as an extension to Eclipse

DTJCB is essentially a plug-in to Eclipse (currently at version 3.0), an open-source Java development tool (see Resources). Eclipse is widely used as the base development environment for several open-source tools and commercial products. DTJCB can be "installed" and integrated into those tools and products. So you can either use DTJCB with your preferred Eclipse-based tool, or you can download the Eclipse IDE and install it as the base Eclipse environment. Add DTJCB to its list of plug-ins, and you're ready for bridging. For the sake of simplicity, we'll refer to any Eclipse-based tool installed with the DTJCB plug-ins simply as "DTJCB."


Figure 1. RJCB bridge structure
RJCB bridge structure

The green boxes in Figure 1 represent the RJCBRT.jar and RJCBRT.dll files that come in the RJCB installation. They provide supporting classes and services used by the generated bridge code (and to a limited extent, by your Java code). The red boxes represent code that's generated by the RJCB bridge generator and are specific to a particular COM API.

Every COM API is described by a special file called a type library. A standalone type library typically has a .tlb extension. Type libraries can also be embedded inside of executable (.exe, .dll, and .ocx) files. The RJCB bridge generator reads a type library and generates the Java and C++ bridge code based on the API that the type library describes.

COM interfaces typically provide two different method-call mechanisms: late-bound and early-bound. Using late-bound calls requires resolving method names at runtime and packaging and unpackaging all the method parameters into or from a special variant array. An early-bound call takes advantage of knowing exactly which method is being called and what its parameter types are. For an in-process COM server (in the same COM apartment), an early-bound call is equivalent to a C++ vtable call (the standard C++ mechanism for calling a virtual method.

The late-bound mechanism is provided by the implementation of a special super-interface called IDispatch. The IDispatch interface provides a method for looking up a COM interface's methods (or properties) symbolically by name, which then returns the method's dispid. The IDispatch interface also provides another method for invoking a COM interface's method given its dispid and an array of variants containing the call parameters. Invoking COM methods via the IDispatch interface is quite inefficient compared to making a simple C++ vtable call (even if you avoid the late-bound feature of IDispatch and just invoke the method with a hard-coded dispid).

The major motivation for the development of the RJCB bridging technology was to create the fastest Java/COM bridge possible. We were bridging some high-traffic APIs between Java and COM, so we needed to achieve the best possible performance from our bridges. For this reason, RJCB bridges support only COM APIs that provide early-bound vtable interfaces. The RJCB-generated bridge code makes direct, interface-specific vtable calls. It does not use the IDispatch superinterface.

The RJCB code generator generates Java proxies for each vtable interface and coclass defined in the COM type library. It also generates Java units containing module constants and enum literals defined in the type library.


Examining example bridge code

For illustration purposes, let's take a look at the bridge code generated for a simple COM API example. This example COM API declares a single module with a few constants, a single enum type with a few enum literals, a single interface that has only a Name property, and a single coclass that implements the single interface. (A more-typical COM API would contain many more interfaces, each containing many methods and properties.)

Listing 1 shows the simple COM API's Interface Definition Language (IDL) specification:


Listing 1. IDL specification for SimpleTestModule
module SimpleTestModule
{
    static const int SIMPLETEST_INT_CONST = 99;
    static const LPCOLESTR SIMPLETEST_STRING_CONST = L"This is a test.";
};


typedef [public] enum SimpleTestEnum {
    STE_VALUE1 =  0,
    STE_VALUE2 =  1,
    STE_VALUE3 =  2
} SimpleTestEnum;


[
    object,
    uuid(1C551D4C-B3D8-4BCA-BDC0-6D870D84CA7F),
    helpstring("ISimpleTest Interface"),
    dual,
    pointer_default(unique)
]
interface ISimpleTest : IDispatch
{
    [propget, helpstring("property Name"), id(1)]
    HRESULT Name([out, retval] BSTR* theName);

    [propput, helpstring("property Name"), id(1)]
    HRESULT Name([in] BSTR theName);
};


[
    uuid(14CED841-ED27-4450-9255-FE384C6C3B0D),
    helpstring("SimpleTest Class")
]
coclass SimpleTest
{
    [default] interface ISimpleTest;
};
    

The corresponding Java source files that the RJCB code generator generates are shown in Listings 2 and 3:


Listing 2. SimpleTestModule.java
package com.ibm.simpletest;

public interface SimpleTestModule {

    public static final int SIMPLETEST_INT_CONST = 99;
    public static final String SIMPLETEST_STRING_CONST = "This is a test.";

}
    


Listing 3. SimpleTestEnum.java
package com.ibm.simpletest;

public interface SimpleTestEnum {

    public static final int STE_VALUE1 = 0;
    public static final int STE_VALUE2 = 1;
    public static final int STE_VALUE3 = 2;

}

Three different Java files are generated for interfaces. One file contains the Java language equivalent of the interface, shown in Listing 4:


Listing 4. ISimpleTest.java
package com.ibm.simpletest;

public interface ISimpleTest {


    public static final String IID = "1C551D4C-B3D8-4BCA-BDC0-6D870D84CA7F";
    public static final Class BRIDGECLASS = SimpleTestBridgeObjectProxy.class;
    public static final String CLSID = "7B422507-1B5B-49F7-BCEF-0FDE519C621A";

    /** 
     * getName. property Name
     */
    public String getName() throws java.io.IOException;

    /** 
     * setName. property Name
     */
    public void setName(String theName) throws java.io.IOException;

}

The second file contains JNI native declarations for each of the methods in the interface, shown in Listing 5:


Listing 5. ISimpleTestJNI.java
package com.ibm.simpletest;

public class ISimpleTestJNI {

    public static native String getName(long native_this) throws java.io.IOException;

    public static native void setName(long native_this, String theName) throws java.io.IOException;

}

The third file contains a Java proxy class that implements the interface by calling the JNI methods. The proxy class also holds a reference to the actual native COM object it represents, in a member variable called native_object (defined in a supporting superclass that all proxy classes extend). Listing 6 shows the Java proxy class file:


Listing 6. ISimpleTestProxy.java
package com.ibm.simpletest;

public class ISimpleTestProxy extends SimpleTestBridgeObjectProxy implements ISimpleTest {

    protected ISimpleTestProxy(String clsid, String iid) throws java.io.IOException
    {
        super(clsid, iid);
    }

    public ISimpleTestProxy(String clsid, String dumb1, Object dumb2) throws java.io.IOException
    {
        super(clsid, ISimpleTest.IID);
    }

    public ISimpleTestProxy(long native_object)
    {
        super(native_object);
    }

    public ISimpleTestProxy(Object com_proxy_object) throws java.io.IOException
    {
        super(com_proxy_object, ISimpleTest.IID);
    }

    protected ISimpleTestProxy(Object com_proxy_object, String iid) throws java.io.IOException
    {
        super(com_proxy_object, iid);
    }

    // ISimpleTest methods

    public String getName() throws java.io.IOException
    {
        String theName = ISimpleTestJNI.getName(native_object);
        return theName;
    }

    public void setName(String theName) throws java.io.IOException
    {
        ISimpleTestJNI.setName(native_object, theName);
    }

}

For a coclass, a single Java coclass proxy class, named SimpleTest, is generated. Listing 7 shows the SimpleTest.java file:


Listing 7. SimpleTest.java
package com.ibm.simpletest;

public class SimpleTest extends ISimpleTestProxy {

    public static final String CLSID = "14CED841-ED27-4450-9255-FE384C6C3B0D";

    public SimpleTest(long native_object)
    {
        super(native_object);
    }

    public SimpleTest(Object com_proxy_object) throws java.io.IOException
    {
        super(com_proxy_object, ISimpleTest.IID);
    }

    public SimpleTest() throws java.io.IOException
    {
        super(CLSID, ISimpleTest.IID);
    }

}

The Java coclass proxy class extends the proxy class of its default interface. If it also implements additional interfaces, then it contains the implementations of the additional interfaces (just like an interface proxy).

The coclass proxy also contains a default parameterless constructor for creating an instance of the COM object it represents. The constructor instantiates the COM object using the CLSID as specified in the coclass declaration in the type library.

The bodies of the native methods declared in ISimpleTestJNI.java are defined in ISimpleTestJNI.cpp, as shown in Listing 8:


Listing 8. ISimpleTestJNI.cpp
JNIEXPORT jstring JNICALL Java_com_ibm_simpletest_ISimpleTestJNI_getName(
    JNIEnv* env, jclass,
    jlong native_this)
{
    SimpleTestLib::ISimpleTest* this_intf = (SimpleTestLib::ISimpleTest*)native_this;
    CComBSTR nativeTheName;
    CHRT(this_intf->get_Name(&nativeTheName));
    return JSTRING_FROM_CCOMBSTR(env, nativeTheName);
}

JNIEXPORT void JNICALL Java_com_ibm_simpletest_ISimpleTestJNI_setName(
    JNIEnv* env, jclass,
    jlong native_this, jstring theName)
{
    SimpleTestLib::ISimpleTest* this_intf = (SimpleTestLib::ISimpleTest*)native_this;
    BSTR_FROM_JSTRING nativeTheName(env, theName);
    CHRTV(this_intf->put_Name(nativeTheName));
}

As you can see in Listing 8, the method bodies simply call the corresponding COM method via the COM interface pointer, doing any necessary parameter conversions (such as converting between the Java language's and COM's string representations). The code also takes care of translating COM error return values into Java language exceptions (via the CHRT* macros).

For bidirectional bridges, where you've indicated to the RJCB bridge generator that certain interfaces in the COM API are implemented in the Java language, two additional files are generated on the C++ side that provide a COM proxy for these Java implementations of COM interfaces. The COM proxy is essentially the same as a Java proxy, except in reverse. It holds a reference to the Java object it represents in a member variable.

In our simple example, the COM proxy files for the ISimpleTest interface are called ISimpleTestProxy.h and ISimpleTestProxy.cpp. These files are a little harder to read because of the complexities of COM programming in C++. Listing 9 shows an excerpt from the ISimpleTestProxy.cpp file:


Listing 9. Code segment in ISimpleTestProxy.cpp
STDMETHODIMP ISimpleTestProxy::get_Name(BSTR* theName)
{
    JNIEnv* env = 0;
    jobject java_object;

    CHRR(_RJCBService->GetJavaObject(this, &env, &java_object));
    JNILocalFrame _JNILocalFrame(env);
    if (theName == 0) {
        return E_INVALIDARG;
    }
    jstring jniTheName = (jstring)env->CallObjectMethod(java_object, _ISimpleTestProxyInfo->
      m_getName_method_id);
    *theName = BSTR_FROM_JSTRING(env, jniTheName).Detach();
    return _RJCBService->CatchException(env);
}

STDMETHODIMP ISimpleTestProxy::put_Name(BSTR theName)
{
    JNIEnv* env = 0;
    jobject java_object;
    CHRR(_RJCBService->GetJavaObject(this, &env, &java_object));
    JNILocalFrame _JNILocalFrame(env);
    env->CallVoidMethod(java_object, _ISimpleTestProxyInfo->
      m_setName_method_id, JSTRING_FROM_BSTR(env, theName));
    return _RJCBService->CatchException(env);
}

Note that the method bodies in Listing 9 simply call the corresponding Java method via the Java object the proxy represents, doing any necessary parameter conversions (such as converting between COM's and the Java language's string representations). The code also takes care of translating Java language exceptions into COM error return values.


Using DTJCB: An overview

To make use of the RJCB technology with DTJCB, you create and build the bridge for a "server" component and then add the code to a "client" component for accessing the "server" through the bridge. DTJCB lets you perform this process within a single environment.

You start with the server component, created in COM or the Java language, then create a bridge inside an RJCB bridge project using the Eclipse environment. The completed project is populated with the bridge proxy code, generated in the Java language and Visual C++, along with the runtime library that the proxy code uses. Then, you use the standard build command in Eclipse to build all the bits and pieces for the bridge.

When you're accessing a COM server from a Java client, you can continue to work inside Eclipse to create the Java project. You add the Java client code that interacts with the proxy in the bridge project, which in turn talks to the COM server via the bridge runtime library. Using DTJCB inside Eclipse enables an end-to-end experience, from creating the bridge to making the actual bridging calls.

When you're accessing a Java server from a COM client, you first register the Java server in the Microsoft Windows environment so it is "exposed" to the COM environment. Then you can use a COM-based development tool, such as Visual C++ or Visual Basic from Microsoft Visual Studio, to write the client code to access the Java server through the bridge. We'll illustrate this process with some examples in subsequent sections.


Developing a Java COM client with DTJCB

Let's look at how to create a bridge for a COM server in a sample DLL file to expose its interface to the Java technology side, as well as invoke that interface from a Java client. Take the example of an office-furniture store running a COM server that tracks its inventory. Its COM interface is exposed from the OfficeFurniture.dll file. You want to pass the inventory information from the COM server into a Java client application.

Creating an RJCB bridge project

To start the bridge-creation process, select the Java-COM Bridge Project entry in the Eclipse New Project wizard, as in Figure 2.


Figure 2. New Project wizard
New project wizard

Clicking on the Next button brings up the new Java-COM Bridge Project page, where you specify the name and location of the project, as in Figure 3.


Figure 3. New Java-COM Bridge Project page
New Java-COM Bridge Project page

The next page shows the bridge(s) currently in the project, if any. It's empty for our new project, as in Figure 4.


Figure 4. Java-COM Bridge Project content page
Java-COM Bridge Project content page

Click on the Add... button to bring up the Java-COM Bridge Settings page, where you provide the information about the COM server and the bridge to be created. Figure 5 shows the page for our example. You designate the name of the bridge, provide the COM server as the source type library, and enter a Java package name for the access from the Java code. (You'll find more options and information in the development tool itself.)


Figure 5. Java-COM Bridge Settings page
Java-COM Bridge Settings page

After you're satisfied with the settings for the bridge you're about to create, click on the OK button. The bridge name now appears in the bridge project content page, as in Figure 6.


Figure 6. Java-COM Bridge Project content page
Java-COM Bridge Project content page

After you click on the wizard's Finish button, you can see that the Package Explorer in Eclipse's Java Perspective is populated by the bridge proxy code, in Java language and Visual C++, as shown in Figure 7.


Figure 7. Generated files in Package Explorer
Generated files in Package Explorer

Building the RJCB bridge project

Now that you've generated the bridge proxy code, you can build the bridge with the standard build menu in Eclipse. Note that the bridge code depends on a JDK (not just the JRE) and Microsoft Visual C++ 6.0 (Service Pack 5), so make sure they've already been installed on the system.

Eclipse supports both automatic and manual build modes, both of which can build the necessary binary files for the bridge. In our example, the build generates the OfficeFurnitureBridge.dll and OfficeFurnitureBridge.jar files.

The bridge is ready to use. You can expand the proxy Java file to see which interface you can use through the bridge. For example, Figure 8 shows the interface methods in OfficeFurniture.dll that you can use on the Java language side.


Figure 8. Interface methods seen through the bridge
Interface methods seen through the bridge

Creating a Java project as a COM client

Following the normal Java code-development process in Eclipse, you can easily create a Java project and write some code to access the interface. Start by using the New Project wizard to create a Java project, as in Figure 9.


Figure 9. New Project wizard
New Project wizard

In the next page, specify the name of the Java project, as in Figure 10.


Figure 10. New Java Project page
New Java Project page

Then, in the next page for Java Settings, you provide the dependencies of the projects and libraries in their respective tabs, as in Figures 11 and 12.


Figure 11. Project dependency settings
Project dependency settings

Figure 12. Library dependency settings
Library dependency settings

When you click on the Finish button, Eclipse creates the Java project. You can now find both the bridge and Java projects in the Package Explorer, as in Figure 13.


Figure 13. Bridge and Java projects in Package Explorer
Bridge and Java projects in Package Explorer

Using the RJCB bridge project from the client project

It's time to add the Java code to invoke the COM server's interface. With the standard New Java class command in Eclipse, you first add a new class to the Java project you just created, as in Figure 14.


Figure 14. Adding new Java class
Adding new Java class

Now add the Java code to reference the COM server interface exposed through the bridge. First, add the code segment in Listing 10 into the body of the Test class's main() method:


Listing 10. New code for the main() method
int count = 0;
try {
// Load RJCB Runtime library
com.ibm.rjcb.RJCBUtilities.loadRJCB
  ("C:\\eclipse\\plugins\\com.ibm.rjcb.dtk.common_1.0.0\\RJCBRT.dll");
// Load Bridge dll
string dll_location = "C:\\eclipse\\workspace\\"
+ "SampleBridgeProject\\MyJ2CBridge\\"
+ "c++\\OfficeFurnitureBridge\\Release\\OfficeFurnitureBridge.dll";
System.load(dll_location);
// Access COM object through the bridge
IOfficeCatalog catalog = new OfficeCatalog();
count = catalog.Count();
System.out.println("count = " + count);
for (int index = 0; index < count; index++) {
System.out.println("item=" + index
+ " ID=" + catalog.GetID(index)
+ " Name=" + catalog.GetName(index));
}
} catch (java.io.IOException e) {
e.printStackTrace();
}

In Listing 10, catalog is a proxy object for the actual COM server, and you can invoke its methods. You can then use the Organize Imports context menu on the class, and Eclipse automatically adds the imports to the class, as in Listing 11:


Listing 11. Imports added by Eclipse automatically
import com.officefurniture.IOfficeCatalog;
import com.officefurniture.OfficeCatalog;

Again, use the build command to build the Java project, and set up the Run configuration as in Figure 15.


Figure 15. Run configuration dialog
Run configuration dialog

The result of executing the Java client is displayed in the Output view, as in Figure 16.


Figure 16. Results in the Output view
Results in the Output view

Pretty nifty, isn't it? You've created Java code that interacts with a COM-based server.


Developing a Java COM server

Now let's look at how to define an interface to be implemented on the Java side while exposed to the COM side through a bridge, and invoke that interface from a COM client. We'll use an interface, ICookie, to illustrate the steps to make this happen. Listing 12 shows the IDL specification of this interface:


Listing 12. IDL specification for ICookie
import "oaidl.idl";
import "ocidl.idl";

[
object,
uuid(364DC55D-9C03-4dc6-AD82-5CAC8F1077FF),
dual,
helpstring("ICookie Interface"),
pointer_default(unique)
]
interface ICookie : IDispatch
{
[id(1), helpstring("method GetModel")] 
HRESULT GetName([retval, out] BSTR *name);

[id(2), helpstring("method GetModel")] 
HRESULT GetKind([retval, out] BSTR *name);
};

[
uuid(658EEF6A-D9C9-4a06-870D-2FA8A31A3026),
version(1.0),
helpstring("COM to Java test 1.0 Type Library")
]
library CookieLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(F0E00502-16E2-43e2-B12C-02EE037C402B),
helpstring("Cookie Class")
]
coclass Cookie
{
[default] interface ICookie;
};
};

Creating an RJCB bridge project

DTJCB supports the creation of the bridge from a type library. So, the trick here is to first use the Microsoft MIDL compiler on the IDL specification in Listing 12 to create a type library we'll call cookie.tlb.

The steps for creating the RJCB bridge project from a type library is similar to the ones we described in the previous section, Developing a Java COM client with DTJCB, except for the specification of the bridge settings. We'll call this bridge project SampleBridgeProject2. You enter the bridge settings as in Figure 17.


Figure 17. Bridge settings for MyC2JBridge
Bridge settings

Notice how the settings in Figure 17 differ from those in Figure 5. The source library is a file with a .tlb extension, as opposed to a .dll extension. This indicates to TJCB that the bridge is created for a Java component as opposed to a COM server. Also, ICookie has a check mark under "Java implemented interfaces."

The rest of the steps are essentially the same as the previous case. At the end of the bridge generation, you can see the bridge project, and the Java proxy for the interface, ICookie.java, as in Figure 18.


Figure 18. Bridge project for SampleBridgeProject2
Bridge project for SampleBridgeProject2

Now you can write a Java class, implementing the ICookie interface, to invoke the server code. To simplify this example, we'll just add a new class whose methods return some simple data. Use the standard Eclipse mechanism to add a class with the new class wizard, as in Figure 19.


Figure 19. New MyCookie class
New class MyCookie

Now provide simple implementations for the two methods, as in Listing 13:


Listing 13. Implementations for GetName and GetKind
public class MyCookie implements ICookie {

/* (non-Javadoc)
* @see com.Cookie.ICookie#GetName()
*/
public String GetName() throws IOException {
return "Butter-n-Sweet";
}

/* (non-Javadoc)
* @see com.Cookie.ICookie#GetKind()
*/
public String GetKind() throws IOException {
return "Buttermilk";
}
}

Building the RJCB bridge project

Now the project is ready to build. Again, because DTJCB is integrated with Eclipse, you can use the standard Eclipse build commands.

Registering the Java COM server

COM technology relies heavily on the Windows Registry. So you need to make sure that the proper components are properly registered, including:

  • The bridge DLL file that DTJCB has generated
  • The JVM that the bridge will run on, along with the proper classpath
  • The Java class that implements the interface and its custom program ID
  • The runtime library DLL file for RJCB

You can register all of these components by using the Windows regsvr32.exe command, and the registerJavaVM.exe and registerJavaClass.exe commands supported by DTJCB. Once the components are all registered, you're ready to put them to work with a COM client.

Using the RJCB in a COM client project

You can create a COM client in several ways. For this exercise, suppose you're using Visual Basic 6.0 in Microsoft Visual Studio. You first create a new standard EXE project and add a reference to the cookie.tlb file using the project/References command. Figure 20 depicts the References dialog that the command brings up and how you can add the reference.


Figure 20. Adding a reference to the project
Adding a reference to the project

Now, add a Command button to the form, as in Figure 21.


Figure 21. Command button on the form
Command button on the form

Then, add the code to make the call across the bridge to retrieve the information from the Java class. Figure 22 shows the code that executes when the user clicks on the Command1 button.


Figure 22. Code that the Command button executes
The code for click the button

Notice that the code instantiates the Cookie object and invokes its methods transparently across the bridge. The result of the execution is that when the user clicks on the Command1 button, a message box comes up and shows the information retrieved from the Java class, as shown in Figure 23.


Figure 23. The resulting message box
The resulting message box

Using standalone RJCB

Use of Eclipse for creating RJCB bridges isn't a requirement. RJCB also comes with its own standalone tools that you can use to generate bridges. You can then use any Java development environment with the generated bridges.

The complete set of RJCB product binaries can be found in the com.ibm.rjcb.dtk.ui_1.0.0\generator folder of the DTJCB installation.

To create a RJCB bridge outside of Eclipse, invoke the bin\generateBridge.exe tool in Graphical User Interface (GUI) mode (that is, with no command line arguments). It presents a form similar to the one presented by DTJCB where you specify the input type library, destination folder, Java package name, and so on. You then click on the Generate or Build button to generate or generate/build the bridge.

You can also run the generateBridge tool in batch mode, with command-line arguments. (This is how the DTJCB Eclipse integration invokes the tool.) Execute the tool with the /? option to see a description of the command-line arguments. The best way to determine which command-line arguments you need for generating a bridge in batch mode is to run the generateBridge tool in GUI mode first. This produces a file called generatebridgename.bat, which contains a batch-mode invocation of the generateBridge tool with all the necessary arguments that correspond to the values you specified in the fields and options through the GUI. This file is created each time you click on the Generate or Build button of the GUI.

Given that Eclipse is the premiere Java development environment in the industry and is available for free (see Resources), we highly recommended that you use it with DTJCB to develop your bridges (see the sidebar, DTJCB as an extension to Eclipse). The integration with Eclipse provides a better all-in-one experience.


Deploying the RJCB bridge

You can deploy an RJCB bridge in a few different ways, depending on the nature of the client that will use the bridge. The client could be a standalone application, an Eclipse plug-in, or a COM based-client. We'll go through these scenarios and talk about what you need to deploy in each one. We'll also touch on how to develop and share the deployed bits in a team environment.

Deploying the "bits" into the client environment

For an application's end user, it is unlikely that the application would run inside Eclipse or the COM-based development tool. The bridge needs to be deployed on the end user's system as a collection of files, including the deployable bridge bits and the RJCB bridge runtime.

In some other cases, the bridge is shared during an application's development. In these cases, you deploy the bridge in the form of an Eclipse plug-in, along with the Common plug-in that hosts the RJCB runtime. This makes the ongoing code development in Eclipse easier.

What are the "bits" for deployment?

With the sample scenarios we went through earlier, you probably have some basic idea of the files you need to deploy for the bridge itself and the RJCB runtime:

  • The bridge executable bits are located in two files: A JAR file and a DLL file. The names of these files are constructed from the name of the source type library filename and a "Bridge" suffix. For example, the bridge created for the MSO.DLL file has its executable bits in MSOBridge.DLL and MSOBridge.JAR.

  • When you deploy the bridge as an Eclipse plug-in, you must deploy the plug-in's JAR and XML files as well so that Eclipse can identify and load the plug-in properly.

  • The RJCB runtime files are part of the so-called Common plug-in, which takes care of loading the runtime DLL and exporting the RJCB.jar file to all bridge plug-ins in Eclipse. When you deploy the bridge as it is (as an Eclipse plug-in) all you need to do is deploy the Common plug-in too. For standalone Java programs the client of the bridge has to take care of this by first copying these files to a convenient location and then either loading the runtime DLL dynamically before first use of the bridge or setting the PATH variable to point to its location. A standalone Java program client also must add the RJCB Java library RJCBRT.jar file to its classpath. Note that explicit loading of the runtime DLL with its absolute path is recommended. This ensures that the right RJCB.DLL version is getting loaded. even if you have more than one version installed on your machine.

More details on deployment scenarios

The following deployment scenarios are based on the type of the RJCB bridge (unidirectional or bidirectional), the type of the client (Eclipse plug-in or a standalone Java program), and whether the deployed bridge will require additional COM registering (as opposed to just copying the deployed bridge files):

  • To deploy a unidirectional RJCB bridge to an Eclipse plug-in client (does not require registering of any DLLs):

    1. Copy the Common RJCB plug-in (com.ibm.xtools.rjcb.common) into the plug-ins folder of the target Eclipse platform folder if it's not already there. The Common plug-in hosts and loads the RJCB runtime.

    2. Copy the RJCB bridge plug-in(s) into the plug-ins folder of the target Eclipse platform folder. Each of the bridge plug-ins has in its <requires> section a reference to the Common plug-in:
      <requires>
      <import plugin="com.ibm.xtools.rjcb.common" export="true"/>
      </requires>
      

    3. Add the plug-in ID (for example, xxx.yyy.zzz) of the bridge plug-in to the <requires> section in the plugin.xml file of the client plug-in(s) from which you want to access the bridge plug-in:

      <requires>
      <import plugin="xxx.yyy.zzz"/>
      </requires>
      

  • To deploy a bridge to a standalone Java client program without registering any DLLs:
    1. Put the bridge JAR file(s) into an arbitrary place on the client machine.
    2. Put the bridge DLL(s) into an arbitrary place on the client machine.
    3. Put the RJCBRT.DLL runtime and the RJCBRT.jar file in an arbitrary place.
    4. Load RJCBRT.DLL with its absolute path prior to the first reference to a bridge:
       RJCBUtilities.load(<absolute path to RJCBRT.DLL>)
      

    5. Add code in your Java client program to load the bridge DLL(s) using System.load before the first reference to the bridge:
       System.load (<absolute path to the bridge DLL>);
      

    6. Add the bridge JAR file and the runtime RJCBRT.jar to the classpath of the Java program.
  • To deploy a bridge plug-in to a standalone client Java program with registering the required DLLs:

    1. Put the bridge JAR in an arbitrary place on the client machine.
    2. Put the bridge DLL in an arbitrary place on the client machine.
    3. Register the bridge DLL using the regsvr32 command.
    4. Put RJCBRT.DLL and RJCBRT.jar in an arbitrary place.
    5. Register RJCBRT.DLL with the regsvr32 command.
    6. Add the bridge JAR file and RJCBRT.jar to the classpath of the Java program.
  • To deploy a bridge (a Java language implemented COM server) to COM clients:

    1. Put the bridge(s) JAR file(s) in an arbitrary place.
    2. Put the bridge DLL in an arbitrary place.
    3. Register the bridge DLLs using the regsvr32 command.
    4. Register a JVM to be used to instantiate the Java server using the RJCB registerJavaVM.exe utility.
    5. Register the Java server class(es) using the RJCB registerJavaClass.exe utility.
    6. Register the runtime RJCBRT.DLL using the regsvr32 command.
  • To deploy a bridge plug-in, exporting it from Eclipse as a deployable plug-in:

    1. Select the bridge project in the Package Explorer of Eclipse.
    2. Bring up the "Export" dialog.
    3. Select "Deployable Plugins and Fragments."
    4. In the deploying dialog pick the "eclipse" folder of the target Eclipse installation as the "Destination Directory" and commit the dialog.
    5. If the bridge is not unidirectional (only Java language to COM) you must register that bridge DLL using the regsvr32 command.

Developing with RJCB in a team

Usually, one person on a development team creates the bridge and puts it under source control so it can be shared among the team members. The following parts of the RJCB bridge project should be put under source control:

  • The following files in the bridge project:
    • .project
    • .classpath
    • plugin.xml
    • build.properties
    • src (subfolder)
    This allows developers joining the project to easily import the RJCB bridge project into the Eclipse environment using Eclipse's "Import" wizard, pointing it to the project location, and also to build and generate the project inside Eclipse.

  • All bridge.xml files -- one per bridge, located in its bridge root folder.

  • The type library source file used to generate the bridge. DTJCB copies this file from its original location to the bridge location inside the RJCB bridge project directory. You do this so the source type library can be shared among team members. (The other team members might not have it or might have it in a different location.)

  • Optionally, the bridge executables can be shared if not all team members have, for example, Microsoft Visual Studio installed and can't build the bridges themselves.

Sharing the RJCB runtime

RJCB bridge clients can either share a single copy of the RJCB runtime or use a private, independent copy of it. To be shareable, the runtime must be configured globally on the developer's machine:

  • Have the directory containing RJCBRT.DLL in your PATH.

  • RJCBRT.DLL is registered with the regsvr32.exe command.

  • Have the corresponding RJCBRT.JAR file on your classpath.

If you have only a unidirectional Java-COM (as opposed to COM-Java language) bridge, you could use a separate copy of the RJCB runtime, which can be even a different version. This is possible because registering the RJCB runtime with the regsvr32 command and placing on your PATH is not required.


Migrating from COM to Java technology

If you've had the patience to reach this point in the article, you probably have a set of Java- and COM-based applications or components that need to be bridged. Although the RJCB bridge provides a way for interoperating between these two types of applications, it would still be a nontrivial task to create all the necessary bridges for a large-scale enterprise solution.

A better strategy for managing these applications or components would be to unify the implementation of all the interconnecting applications or components into a single technology. This would save a lot of the effort you'd expend tackling the interoperability issues in the underlying componentry, and allow you to direct time and resources to the real business problems to be solved. With this as our goal, let us look at how you can migrate all the components to be become Java-based.

Strategy: Revolution versus evolution

Let us assume that an enterprise has a mix of Java and COM components in an existing application and would like to turn them into all Java components. If the enterprise has lots of time and resources, it might be able to revamp the design and implementation of the entire application from scratch in Java programming language. This effort would likely lead to clean architecture, simple development effort, and low maintenance effort. On the down side, the risk would be pretty high, and the impact of switching from one application to another could be daunting. This would work well if the application is small-scale.

The reality usually turns out to be more challenging. The enterprise might not have the time and resources to go for such a large endeavor, so a gradual migration might be a more realistic plan. It would be good to replace COM components by Java components one-by-one, and eventually turn all the components of the application into Java technology. An immediate issue that comes to mind is how you can manage these small steps without causing major havoc with regard to the application's availability

Migration with RJCB

The RJCB bridge can help keep all the components together. When you decide to convert a COM component to Java technology, the new Java implementation of the component can directly interact with its dependent Java components. For the dependent components that are still in COM, you can create an RJCB bridge for interfacing those components with the new Java implementation. This way, once the new implementation is completed, it is ready to interoperate with the rest of the components in the application.

By taking such small, incremental steps, you can keep an application available for use almost without interruption, and eliminate the final application switch when the migration is complete. This removes the huge risks associated with the revolutionary approach, and it gives the migration plan a better chance to succeed.


Final remarks

Java technology and COM are the two backbone technologies in the world of component-based architecture today. As long as they continue to be the major players in component interoperability, there'll be a need to bridge the gap between the two technologies. The RJCB bridge is an effective solution, and DTJCB gives you a versatile tool for implementing it.


Resources

About the authors

Cheng-Yee Lin is a development manager for the IBM Rational modeling products. He has been involved in several projects related to compilers, UML, and API-based integrations. Mr. Lin holds M.S. and M.Ph. degrees in computer science from Yale University.

Tom Houser is the original developer of the Rational Java/COM Bridge technology. Prior to joining IBM, he worked in the software tools development industry for 24 years, working on compilers, debuggers, and modeling tools. Mr. Houser holds a B.S. in computer science from the University Wisconsin at Madison.

Peter Parapounsky is a software engineer at Rational Software, IBM Software Group. For the past five years, he's been working on different aspects of the Rational XDE product. Mr. Parapounsky holds an M.S. in computer science from the Technical University of Sofia, Bulgaria.

Comments



Trademarks

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=31912
ArticleTitle=Integrate COM and Java components
publish-date=11162004
author1-email=clin@us.ibm.com
author1-email-cc=jaloi@us.ibm.com
author2-email=tmhouser@us.ibm.com
author2-email-cc=jaloi@us.ibm.com
author3-email=pjparapo@us.ibm.com
author3-email-cc=jaloi@us.ibm.com

My developerWorks community

You tell us!

developerWorks wants to get to know you

How does the developerWorks community use social media?


Special offers