Master of disguises: Making JavaBeans look like ActiveX controls

Learn how to make your JavaBeans available to Windows programmers using tools from Sun and Microsoft

Life's tough when you're trying to develop reusable software components. Often, the development tools your customers use dictate which development tools and architecture you choose. As a component developer, you may be attracted to the simple, object-oriented architecture of JavaBeans. However, there are a lot of Visual Basic (Visual C++, Delphi, etc.) developers who want ActiveX controls, not JavaBeans. Are you ready to forfeit these potential customers? Wouldn't it be great if you could write your components in Java and make them available to ActiveX control users? Well, you can. And in many cases, you won't even have to modify your existing Java code!

The right tool for the job: Choosing between the Sun and Microsoft tools

Currently, both Sun and Microsoft have bridging tools that are freely available for download: the Packager application from Sun and javareg from Microsoft (see Resources). Which one you choose will depend a lot on your personal tastes. Each has its strong and weak points. In general, users of your bridged JavaBeans won't see much difference between the two solutions. Both solutions utilize a Java virtual machine (VM) that is loaded as a dynamically linked library (DLL) into the client application or development tool. It's important to note that both solutions load only one instance of the VM per client. This allows an application to "connect" multiple ActiveX controls to each other, and know that that their underlying JavaBean instances are running in a shared VM.

Unfortunately, neither of the toolsets earns points for its robustness. Both do a rather poor job of helping you diagnose problems with your controls. However, I did find Sun's BeanBox testing tool helpful in validating JavaBeans prior to running either bridging tool. In addition, both tools suffer from a lack of good documentation (as if anything ever comes with good documentation, right?) Fortunately, Sun provides reasonably responsive e-mail support for its JavaBeans Bridge. While Microsoft's free support is limited, the Java-COM list server is a good place to look for answers specific to tools. (See Resources for information on how to access these support services.)

Sun's JavaBeans Bridge for ActiveX 1.0

The first tool we'll examine is the Packager application, which comes as part of Sun's JavaBeans Bridge for ActiveX. Packager allows you to:

  • Generate Windows-friendly event interfaces
  • Generate standard, easy-to-use registry files
  • Require separate jar files for ActiveX users

We'll examine each of these aspects in the following discussion.

Packager is a Java application that has both graphical and command-line interfaces. To use it, you place your JavaBean(s) in a jar file along with any supporting code or resources. Note that your beans must belong to a package other than the default package, otherwise the Packager will crash, leaving you with nothing but a cryptic error message about string parsing.

Packager's GUI guides you through the process of bridging your JavaBeans. You first select the jar file and JavaBean you wish to bridge, as well as the name for the ActiveX control and destination directory for the generated files. One nice feature is Packager's ability to "uncrack" Java events so they're delivered as their component parts -- much like events from "native" ActiveX controls -- rather than single event objects. The example below shows a typical Java event class:

public class GameEvent extends java.util.EventObject
{
    public GameEvent(Object source, int level, 
                     int numClicks, boolean won) {...}
}

If you don't select the uncrack option, a Visual Basic event handler method might look something like this:

Private Sub Blackout1_mouseClicked(ByVal GameEvent1 As Object)
    ' Get the number of clicks from the event 
    ' using the IDispatch interface
    Clicks.Caption = GameEvent1.getNumClicks
End Sub

Notice that the method receives the GameEvent instance as a generic Visual Basic Object type. Because Visual Basic supports late binding, the GameEvent.getNumClicks method can be invoked on the generic object. Behind the scenes, this call is passed to the Java object. (More on how this works later.) However, Visual Basic's name-completion capabilities will be unavailable to the user and invocation errors won't be detected until runtime.

Event handlers for uncracked events receive the event as a list of its component parts. Here's a handler, which has been uncracked, for the same event:

Private Sub Blackout1_mouseClicked(ByVal numClicks As Long, ByVal Level As Long, ByVal won As Boolean, ByVal source As Object)
    Clicks.Caption = numClicks
End Sub

Uncracking your events has two ramifications. First, because the event object is no longer passed into the event handler, any custom methods you've added to your event will be unavailable. Second, because event handler receives copies of the data in the event, it predicates that your event's fields are immutable.

After you select all the generation parameters, click on the Start Generation button to put Packager to work. Packager first generates adapter classes and adds them to your jar file. At this point your jar can only be used in an environment where the Sun ActiveX bridge classes (in the sun.beans.ole package) are available. If you want to distribute your JavaBeans to environments that will not have these classes, you'll need to ship a copy of the jar file that hasn't been run through the Packager.

Sun's Packager generates adapter classes for your JavaBean

Next, Packager generates type library (tlb) and registry (reg) files for the JavaBean. The registry file contains information about the ActiveX control, including the location of its type library and jar files. This file is loaded into the registry so that ActiveX client applications can access the control. Users of your controls can include the registry file with their applications and/or incorporate its contents in their application installation scripts.

The type library contains information about the COM (more on COM later) interface(s) supported by the control. Applications use this file to provide API information to the control user. Packager preserves your Java method parameter names, making the controls easier to use (Microsoft's javareg names parameters as Parameter0, Parameter1, etcetera). Although this file is binary, you can examine its contents with the OLE-COM Object Viewer that comes with Visual C++. You'll notice that this file looks a lot like any other type library. There's really nothing Java-specific about it.

Users of your control won't necessarily know they're using a JavaBean. It will appear along with the other ActiveX controls and have almost all the same characteristics found in controls written in other languages like Visual C++. It will, however, have one nifty addition. If you've gone to the trouble to write a customizer for your JavaBean, it will be available as a custom property page in ActiveX environments.

When a JavaBean/ActiveX control is loaded into a development tool or application, the registry information instructs the system to load the Java virtual machine as a dynamically-linked library (assuming it hasn't already been loaded) and create an instance of your JavaBean class. Subsequent method calls and events are passed between the application and the control via the adapter classes generated by the Packager.

When deployed, JavaBeans that have been bridged with the Packager will require the following items to be installed on your customer's machine:

  • The Java Development Kit (JDK) or Java Runtime Environment (JRE), version 1.1 or higher
  • The JavaBeans Bridge for ActiveX or JavaBeans Bridge for ActiveX Runtime
  • The jar file you ran through the Packager
  • The registry and type library files that were generated by the Packager

Note that the registry file contains some explicit directory information. You may need to modify the values to match the installation directories used on the customer's machine. Do this before the file is loaded into the registry.

Microsoft's Software Developer's Kit (SDK) for Java 3.0

Next we'll examine Microsoft's tool, javareg. Although javareg performs the same task as Sun's Packager, its approach is somewhat different. The javareg tool allows you to:

  • Register most Java classes a COM objects
  • Support low-level COM/DCOM as well as ActiveX
  • Require a special registration tool to be shipped with your controls

We'll examine each of these capabilities in detail in the following discussion.

javareg comes as part of Microsoft's SDK for Java. In addition to registering JavaBeans as ActiveX controls, it provides two capabilities currently unavailable from Sun's tools. First, it allows Java classes to be registered as standard COM/DCOM objects, as well as ActiveX controls. Second, it can register almost any Java class, regardless of whether or not it's a JavaBean. This supports low-level COM integration and also facilitates the implementation of user-defined types as JavaBean method parameters and return types.

In addition, the Microsoft SDK includes a package (com.ms.com) of classes that provide enhanced COM capabilities, including the ability to use COM objects written in other languages within Java code. If you're looking to integrate closely with COM, these capabilities will probably compel you to use javareg and the other tools in the SDK. This article, however, will focus on the ActiveX control registration, and leave the low-level COM discussion for another time.

Prior to using javareg, you should make sure that your JavaBean classes are accessible within the classpath. Next, you invoke javareg, from the command line, with parameters that specify how your JavaBean is to be registered:

javareg /register /control /codebase:. /class:blackout.Blackout /clsid:{3BFE1750-0C76-11d2-B0FA-00A024BA2CD9} /typelib:Blackout.tlb

In this example, the command-line arguments indicate that we're registering an ActiveX control from the blackout.Blackout Java class, and that the files can be located in the current directory. The CLSID parameter specifies a globally unique identifier (GUID) for the ActiveX control being registered. Most applications use the CLSID (rather than the name) to retrieve information about an ActiveX control. For this reason, control developers rarely change a control's CLSID. Providing the CLSID parameter allows you to ensure the same value is used any time the control is registered. If you don't specify the CLSID on the command line, javareg will create a new CLSID for the JavaBean. (Note: You can have explicit control of the CLSID with Sun's tools by editing the CLSID entry of the reg file generated by the Packager.)

Unlike the Sun tool, javareg doesn't generate adapter classes to implement bridging. Instead, Microsoft built the bridging smarts directly into its Java virtual machine. This eliminates the need to maintain a separate jar file for ActiveX installations. The javareg tool also bypasses generation of a registry file by directly updating the registry. This is unfortunate, as it limits your access to the information being inserted into the registry.

For example, while javareg does allow you to set the CLSID for your control, there are other registry identifiers, including one for the type library, that it generates every time it's run. Because Visual Basic applications use the type library identifier, the Visual Basic developer must repair applications that use a bridged control each time it is re-registered, even though the control itself may not have changed. In addition, customers of your controls will also need to incorporate javareg in the installation scripts for their applications. The good news is that many of the registry entries are boilerplate, so with a little effort you can solve these problems by generating your own reg file. However, it would be much nicer if the tool did this for you!

The javareg tool does, however, generate a type library. If you examine this file and the one generated by the Sun tool, you'll find the two are nearly identical. Inside the type library you'll find declarations for the public interface of your JavaBean, as well as the interface for objects that will receive events generated by your JavaBean. Both javareg and Sun's Packager will generate this information from your BeanInfo class if you created one for your JavaBean. Otherwise, introspection is used to generate the type library information.

Although javareg differs from Sun's Packager in the way it bridges your JavaBeans, usage of the controls is the same. The controls appear as normal ActiveX controls. When they're loaded into an application, the Microsoft Java VM is also loaded and an instance of the JavaBean is created.

When deployed, JavaBeans that have been bridged with javareg will require the following items to be installed on your customer's machine:

  • A recent version of the Microsoft Java virtual machine (msjava.dll)
  • The javareg.exe program from the Microsoft Java SDK
  • Your JavaBean and its supporting files

javareg will need to be run on the target machine. You may want to ship registration scripts and the type libraries, to ensure that controls are generated identically for all customers.

A simple example

The applet below is a game built around the Blackout JavaBean. The goal of the game is to "turn off" all the buttons. Clicking a button toggles its state and also toggles the buttons immediately above, below, left, and right. Now might be a good time to take a break from reading -- and play a couple of games!

You need a Java-enabled browser to see this applet.

Note: You must use a 1.1/AWT-enabled browser to view and manipulate Blackout.

Creating a Visual Basic 5.0 version of this applet is quite easy. Simply follow these steps:

1. Download the Blackout source code (see Resources). Compile the JavaBean, and create a jar file for it and its supporting files

2. Run the bridging tool of your choice to register Blackout as an ActiveX control

3. Add the Blackout control to your Visual Basic project

The picture below shows the Visual Basic 5.0 Components dialog box, with the bridged Blackout control listed along with the other ActiveX controls.

Your JavaBeans appear with other ActiveX controls

Note that the location information at the bottom of this dialog box shows the location of beans.ocx, Sun's ActiveX bridge. If the control had been registered with javareg, the location information would have been for the msjava.dll file.

4. Drag and drop the Blackout control onto your form. Set its properties.

Using the Blackout JavaBean within Visual Basic

5. Add code to your project to control Blackout and display results.

When the Blackout control is added to the project, Visual Basic reads in the type library information for it. Method descriptions will appear in Visual Basic's Object Browser and automatic line completion pop-ups in the editor.

6. Play!

7. Explain to your boss that you're really learning about JavaBean/ActiveX bridging, not simply playing games on company time!

ActiveX 101

If you haven't already done so, you will need to become acquainted with the world of ActiveX. At the heart of ActiveX is Microsoft's Component Object Model (COM). The COM specification provides a programming model, similar to CORBA, that allows software to connect at a language-independent object level.

Like CORBA, COM provides an interface definition language (IDL) for describing COM objects. Type library files contain a compiled version of this information. The OLE-COM Object Viewer allows you to look at the decompiled version of a type library. Below is a portion of the type library for the Blackout control:

library Blackout
{
    dispinterface BlackoutSource {
        properties:
        methods:
            [id(0x00000000)]
            void mouseClicked(
                            [in] long numClicks, 
                            [in] long level, 
                            [in] VARIANT_BOOL won, 
                            [in] IDispatch* source);
    };
    dispinterface BlackoutDispatch {
        properties:
            [id(00000000), helpstring("The maximum number of clicks 
                                       allowed while playing a game.")]
            long MaxClicks;
        methods:
            [id(0x00000001), helpstring("Returns the number of times the 
                                         player has clicked in the 
                                         current game.")]
            long getNumClicks();
            [id(0x00000002), helpstring("Sets the game level of the 
                                         Blackout.")]
            void setLevel([in] long parm1);
    };
    coclass Blackout {
        [default, source] dispinterface BlackoutSource;
        [default] dispinterface BlackoutDispatch;
    };
};

This library defines two interfaces, BlackoutSource and BlackoutDispatch. BlackoutSource describes the methods and properties of the control, and BlackoutDispatch describes the event listener interface. The library also defines a COM object class, with the list of interfaces it implements. Clients of COM objects gain access to the object in two steps. First, the client obtains an instance of the object, either by name (ProgID) or by unique identifier (CLSID). Next, the client must query the object to determine whether that object implements a particular interface. The client application knows what interfaces it can use, but it may not know anything about the objects that implement those interfaces.

The two interfaces in the above library are special. The dispinterface keyword indicates that they are "dispatch" interfaces. This mechanism is the cornerstone of what Microsoft calls OLE Automation. All ActiveX controls implement a dispatch interface. Normally, when a client queries an object for an interface it receives a pointer to a v-table. Early-binding languages like C++ use this pointer to invoke methods directly on the object. Dispatch interfaces allow languages that use late-binding (like Visual Basic and VBScript) to invoke methods using a logical ID or method name. These clients use the IDispatch interface to access COM objects. This interface provides a method, Invoke, which takes as a parameter a dispatch ID, corresponding to a method ID in the type library. The COM object (in this case the JavaBean/ActiveX adapter code generated by the bridge) that implements this interface uses the dispatch ID to look up and invoke the desired method.

Type issues

Up to this point the picture has been quite rosy. The Blackout example is rather basic, particularly where parameter and return types are concerned. However, complications arise when you start using something other than integers and strings for your method parameters and return types. The table below shows how certain data types are marshaled between Java and the ActiveX client:

JavaMicrosoft SDK for Java 3.0Sun JavaBeans Bridge for ActiveX 1.0
Type LibraryVisual BasicVisual C++Type LibraryVisual BasicVisual C++
booleanVARIANT_BOOLBooleanBOOLVARIANT_BOOLBooleanBOOL
bytelongLonglongshortIntegershort
byte arrayVARIANTArray (Long)SAFEARRAY (long)VARIANTArray (Byte)SAFEARRAY (short)
charlongLonglongshortIntegershort
char arrayVARIANTArray (Long)SAFEARRAY (long)VARIANTArray (Integer)SAFEARRAY (short)
doubledoubleDoubledoubledoubleDoubledouble
floatdoubleDoubledoublesingleSinglefloat
Integer (object)IDispatch*StringIDispatch*longLonglong
intlongLonglonglongLonglong
long (8 bytes)long (4 bytes)Long (4 bytes)long (4 bytes)long (4 bytes)Long (4 bytes)long (4 bytes)
Object (or subclass)IDispatch*ObjectIDispatch*IDispatch*ObjectIDispatch*
shortlongLonglongshortIntegershort
StringBSTRStringCString (MFC)BSTRStringCString (MFC)

As you can see, the Sun and Microsoft bridges marshal data somewhat differently. The Sun bridge, for example, marshals the Integer class as a COM long-integer. The Microsoft bridge, on the other hand, marshals it as it would any other Object, providing the IDispatch interface for it.

In addition, some types, like byte and long, cause problems for both bridges. In tests, both bridges marshaled a byte with a value of 0xff as its signed, two's-complement equivalent of -1. If, however, 0xff is a byte in an array and you're using the Sun bridge, it's interpreted properly (as decimal value 255). Java long values cause a problem because they exceed the capacity of the long integer types to which they're marshaled, which could lead to data loss.

The most significant area of concern is the marshaling and use of Java objects as method arguments and return values. Both bridges expose Java objects as generic automation objects that implement the IDispatch interface. As I mentioned earlier, Visual Basic developers will see these as instances of Object. They can simply invoke methods on this Object and Visual Basic will do the dirty work of using IDispatch.

In Visual C++, however, things get a little more difficult. If you bridge with Sun's tools, the Visual C++ developer will need to program directly to the IDispatch interface. This approach isn't very appealing (and after numerous attempts, I've yet to be able to get it to work). Here, though, is where javareg's ability to register almost any Java class becomes very appealing. You simply register the method parameter/return-type classes along with your JavaBeans. Visual C++ can then use the type library information for these classes to create C++ wrappers that use IDispatch internally, but provide a much friendlier interface. The Visual C++ example I've provided for you in the Resources section shows this technique in action. Keep in mind that your Java class must have a default constructor if you wish to create instances from within a COM client.

Linking JavaBeans together

In the real world, controls do not simply interact with the application containing them. Often they need to interact directly with other controls within the application. As I mentioned earlier, use of a single VM per process allows multiple JavaBean controls to work together just as they would if they were running within a Java application. If your controls will be used from within Visual C++, however, there are a couple of gotchas lurking for you. Let's look at two JavaBean controls that work together:

public class MyBean extends java.awt.Component implements java.io.Serializable
{
    public String getSomeText() { return "Hello World!" };
}
public class MyBeanUser extends java.awt.Button implements java.io.Serializable
{
    public void setMyBean(MyBean b)
    {
        setLabel(b.getSomeText());
    }
}

In a Java application, linking instances of these two controls might look as follows:

_myBeanUser.setMyBean(_myBean);

The code in Visual Basic looks nearly the same. However, Visual C++ wraps controls in a subclass of the MFC class CWnd. This wrapper prohibits direct access to the bean instance. Thus, in Visual C++, the above code won't work. The workaround is to place a method in your control that returns an instance of itself, as follows:

public MyBean getInstance()
{
    return this;
}

This may seem to be a ridiculous method (along the lines of calling a friend to ask for his or her phone number), but it places a method in the Visual C++ wrapper class that allows you to directly access the bean instance. You can then use this method when linking your controls. For example:

_myBeanUser.setMyBean(_myBean.getInstance());

Unfortunately, if you used Microsoft's tools to bridge the above JavaBean, you'll notice that the getInstance will cause your Visual C++ application to throw an exception when it exits. The cause seems to be a reference count that is never decremented. Whether this is a bug or by-design remains to be seen (I've logged a bug with Microsoft, but haven't yet received a response). In either case, if you want to use the Microsoft bridge and need to link your controls from within Visual C++, your only option, currently, may be to completely wrap your JavaBean with another class that will only be used by ActiveX control clients. For example:

public class MyControl extends java.awt.Component implements java.io.Serializable
{
    private MyBean _instance;
    public MyControl() { _instance = new MyBean(); }
    public MyBean getInstance() { return _instance; }
    public String getSomeText() { return _instance.getSomeText(); }
}

You're probably groaning right about now, but it's not as bad as it seems; there are certain advantages to this approach. Placing a layer between your JavaBean and the ActiveX world may also allow you to mitigate type-marshaling issues. For example, most Windows developers aren't familiar with Vector. They are, however, accustomed to the SAFEARRAY type, and Microsoft does provide a Java version of it within the SDK. You can use your wrapper class to handle the conversion between SAFEARRAY and Vector. The downside of this is, of course, that you're now writing additional, nonportable, code to support a certain segment of users. At this point you may want to step back to decide whether you prefer to do this, to dual-implement the controls (for example, one set in Java and another in Visual C++), or to throw in the towel and forget deploying to ActiveX customers altogether. Check out the sidebar " Bridge over troubled waters?" for more on making this decision.

Other issues

In addition to the issues I've already noted, you need to be aware of a number of other hurdles facing you when you set out to bridge JavaBeans. One of the first roadblocks you'll notice is that COM currently doesn't support method overloading. Both javareg and Packager will choose one of the overloads for the type library. Also missing from COM are class constants. If your JavaBeans expose constants you'll need to either document the values and/or ship constants files (as h or bas files) for use by ActiveX developers.

Debugging JavaBean/ActiveX controls can be challenging. While Sun's bridge sends Java console output to the same location as application output, Microsoft's doesn't. For the most part, controls function the same within an ActiveX application as they do within a Java environment. However, you should be sure to test the controls using the same virtual machine as will be used by the bridge. If you test with Sun's VM but bridge with Microsoft's javareg, you may be in for an unhappy surprise.

Component versioning is another area where there seems to be more questions than answers. This isn't a new issue, but bridging does complicate things a bit. The techniques used to version COM controls differ from those used to version JavaBeans, and the bridging tools do little to help. If your controls are only used in-house, you may be able to manage your software to avoid this problem. However, if you plan to distribute your controls publicly, you will need to put some thought into how you will handle versioning.

Conclusion

There you have it: a great idea, some promising tools, but a lot to be desired. Can you use this technology in real life? The answer to that question will depend on your controls, your comfort with COM, and your ability and desire to work around the tools' shortcomings. What is important, however, is that bridging, while still in its infancy, has significant potential to allow you to maximize the usability of your controls without having to duplicate programming effort or shun certain customer segments in favor of others.

Patrick Carey is a software engineer at XYPOINT Corporation (http://www.xypoint.com/), a location services company in Seattle, WA. Patrick's current projects include a Java application that allows 911 dispatchers to see the location of a wireless phone on a map, and a library of JavaBeans/ActiveX controls that allow other companies to use XYPOINT's services in their own applications.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more