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

1 2 3 Page 2
Page 2 of 3

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.

1 2 3 Page 2
Page 2 of 3