Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java and COM Automation


Dr. Dobb's Journal January 1998: Java and COM Automation

Taking the edge off dual interfaces

Kenneth is a software developer for Lehman Brothers Inc. in New York. He can be contacted at [email protected].


Java and ActiveX are usually portrayed as competing technologies. Yet many of us need to work with both, and there are compelling reasons to do so. In this article, I'll examine the ActiveX technology called Automation (formerly "OLE Automation"), and show how it can be implemented in Java. Automation provides a good way of packaging APIs because it is accessible from a wide range of desktop applications, scripting tools, and languages.

Java, as implemented by Microsoft in its compiler and Java Virtual Machine (JVM), simplifies several aspects of the COM technology on which Automation is based, and provides a clean way to implement Automation. Java code for Automation is, by and large, the same as other Java code -- the elaborate efforts that C++ programmers must exert to create objects, maintain reference counts, and obtain interfaces are taken care of by the JVM.

Java is also a superb language for efficient cross-platform network communications. Java serialization means that Java objects automatically have a canonical representation for network transport, one that can be generated or interpreted essentially without any extra code.

What is Automation?

An Automation API is a collection of dispatch interfaces. A COM interface is, at the binary level, a pointer to an array of function pointers. To use a COM interface, a client must know the layout of this array in advance. That is, it must know which functions occur in which order and what parameter and return types they have.

Early versions of Visual Basic were unable to accommodate such knowledge and used the IDispatch interface to determine the methods and properties of objects at run time. While Visual Basic eventually gained the ability to deal with COM interfaces, other scripting languages (such as Perl, VBScript, and JavaScript) still require this sort of run-time binding.

Dual interfaces combine the static binding of the array of function pointers with the dynamic binding provided by IDispatch. A dual interface provides efficient access for clients that understand static binding (such as C++ or more recent versions of Visual Basic), but still supports scripting clients.

Dual interfaces can be difficult to implement. MFC arguably makes it even more difficult in C++. ATL helps, but is a bit arcane. In Java, dual interfaces are free, once they have been defined.

Interfaces in COM are specified in Microsoft's Interface Definition Language (MIDL). Such a specification generates marshaling stubs for interprocess or remote (or, in some cases, interthread) invocations and a type library (a binary-encoded description of a set of interfaces). Clients can use the type library to browse your API and view its properties and methods. From the type library, you can generate code that binds your Java classes to COM interfaces.

An Easy Way

Version 2.0 of Microsoft's JVM provides "Automatic IDispatch," which lets any Java class present an IDispatch interface. Simply register the class as a Java object using the javareg command. In Listing One the autoXTest class is registered as a COM object. The path to the class file is given in the CODEBASE option. progid provides a convenient way for clients to refer to the COM object class without using the 128-bit class ID, which the command will generate.

Any Automation client can create a class registered in this way. If any of its methods return other classes, they can also be used by the client (even if they are not registered).

This is a nearly effortless way to create an Automation API, but it has several limitations. First, you cannot provide dual interfaces this way -- only dispatch interfaces. Second, you cannot specify certain features that allow your API to conform to the recommended guidelines and work smoothly with Visual Basic clients.

You can overcome these problems by using MIDL to define your interfaces.

The Address Book API

To illustrate, I'll implement an address book that is capable of storing, searching, and retrieving address entries. Complex object models (such as those found in Excel) can be built from these techniques.

The address book uses Microsoft's Java SDK 2.0, along with Visual J++ 1.1. Confusingly, J++ 1.1 implements the 1.0 version of the Java language, whereas the 2.0 SDK implements the 1.1 version of the language. This version implements new Java language features such as serialization and inner classes, and allows a Java COM server (API) to run in process (that is, as a DLL).

Microsoft's Visual J++ 1.1 can be made to work with its Java SDK 2.0. If you plan to use both, I recommend you install Visual J++ first. Then obtain and install the SDK; see http://www.microsoft.com/java/. Finally, copy the following files into your DevStudio\SharedIDE\bin directory: jvc.exe, jps.dll, and msjvc.dll. You also might want to save the original version of jvc.exe somewhere. Visual J++ works reasonably well with the SDK. I found some flakiness with the debugger, but I was using a beta version of the SDK.

Figure 1 shows the four main interfaces making up the Address Book API.

  • The Application interface is the client's first point of access to the library.
  • The AddressBooks collection contains all the currently open AddressBook files.
  • The AddressBook interface provides a collection of Address entries.
  • The Address interface presents properties for a name, address, and phone number. An AddressBook can locate an Address entry by name, or list all its entries via an enumeration interface.

Both the AddressBooks and AddressBook collections provide two kinds of enumerations: IEnumVariant and DIEnum (dual-interface enumeration).

The MIDL file in which these interfaces are defined, AddressBookLib.idl, is available electronically (see "Resource Center," page 3). This file is compiled into a Type Library file (.tlb) using the MIDL command. From the type library, you generate Java interfaces using the JActiveX tool (in the SDK). You then write implementation classes for these interfaces.

Listing Two (excerpted from AddressBookLib.idl) shows the definition for the Application interface. All elements of an interface can (and sometimes must) have one or more attributes, which are enclosed in square brackets. All COM interfaces have the object and uuid attributes. Each must have a unique uuid.

The uuid can be generated with guidgen, a tool provided with Visual J++ as well as with the Java SDK. The guidgen tool generates a uuid and can put it on the clipboard in several formats. The Registry format is the most convenient for this purpose. A sample result is {189636C0-32BD-11d1-B5AC-9E4A44000000}. You can paste this between the parentheses of the uuid() keyword, then delete the curly braces ({}).

Dual interfaces require the dual attribute. The pointer_default attribute specifies the handling of pointer parameters with regard to aliasing and other issues, and is required for most interfaces. pointer_default(unique) is generally the proper value.

Interfaces, as well as methods and other elements in MIDL, can have help strings that appear in object browsers.

A method has an ID, which is used by IDispatch. The ID is generally arbitrary and can be omitted (in which case, one is assigned by the MIDL compiler), but certain values have special meanings. In Listing Two, the Name property has the special ID DISPID_VALUE indicating that Name is the default property for this interface.

Properties appear exactly like methods. Each property can have a get and/or set method. These are indicated with the propget and propput attributes, respectively. Like interfaces, methods and properties can have help strings.

Each parameter has an indicator to show whether it is an input or output parameter, or both. The retval keyword indicates a return value. Although methods in a dispatch interface always return HRESULT, they do not appear that way to the client language (unless that language is C++). Instead, the HRESULT is treated as an exception code (an error code in Visual Basic), and the return value is the one indicated with retval, if any. The retval must be the last parameter, and must be an output parameter (that is, have the out keyword).

Output parameters are always pointers. When the output is an interface pointer, it must be coded as a pointer to a pointer (**). Table 1 lists the legal types for parameters and return values in dispatch interfaces.

The Application interface has an AddressBooks property that returns an AddressBooks interface pointer. In addition, it has Application and Parent properties. All Automation objects should have these two properties. In the case of the Application interface, they simply return pointers to the interface itself.

Listing Three is the Application interface in Java, as generated by the JActiveX tool. HRESULTs are nowhere to be seen: The return values are those indicated with the retval attribute. HRESULTs are provided through exceptions.

The first statement in Listing Three is the package declaration. The package name is derived from the library name as declared in the MIDL file.

The generated code is full of special comments that look like Javadoc comments, starting with /**@com. These instruct the compiler to generate special codes in the class file to support the COM mechanisms.

Listing Four is the declaration of the coclass (again, from AddressBook.idl) associated with the Application interface. An Automation API must provide at least one coclass so that the client can gain access to the library. The client instantiates an object of this class, and uses its methods and properties to obtain other objects. The uuid for the coclass forms the key value for a Windows registry entry that tells COM how to load the object's server and create an instance of the object. JActiveX generates a source file for the coclass. This file contains everything you need to instantiate your class through COM.

In Visual Basic, a statement such as Set obj = CreateObject("AddressBook.Application") results in a call to a COM API function (probably CoCreateInstance) that creates an instance of this coclass and obtains its dispatch interface.

Listing Five is the AddressBook interface declaration. The AddressBook object, like AddressBooks, is a typical COM collection class. Beyond the obligatory Application and Parent properties, it has an Add method to create new Address entries, and a Remove method to delete them. The Item method provides access to specified elements in the collection. Item is generally indexed, but the type of index depends on the collection. In this case, the index is a string representing a name. These methods are required in the Automation guidelines.

The guidelines also require a collection to support the _NewEnum property, which returns an object that implements the IEnumVariant interface.

This COM interface supports the Visual Basic "for each" construct. Since the VB construct is less convenient to use from C++ and impossible to use from Perl or VBScript, my enumeration object also supports my own DIEnum interface.

The AddressBook has properties and methods appropriate to document objects, including the FullName, Name, and Path properties. These give portions of the filename. The Saved property indicates whether the file has been modified since it was last saved. The Save method saves current changes, and the Close method removes it from the list of open AddressBook objects (that is, from its parent AddressBooks object).

The class AutoEnum (from AutoEnum.java, also available electronically) implements the IEnumVariant and DIEnum interfaces. It requires a reference to a collection object, from which it obtains an Enumeration. I've defined an interface called Enumerable (available electronically) that supports a single method, elements(), returning an Enumeration. I couldn't wrap the Enumeration directly because IEnumVariant provides a Reset method, which must obtain a new Enumeration from the collection.

The Next method (Listing Six) returns its results in an array of VARIANTs. Among the data types supported by the VARIANT union is an IDispatch pointer. Microsoft provides a Java wrapper class for the VARIANT type. I use the putDispatch() method. There is a put method for each data type supported by VARIANT.

Beyond the AutoEnum class, the rest of the example involves straightforward Java code. The beauty of these tools is that all the COM machinery -- with which C++ programmers generally must deal explicitly -- is buried in the compiler and the JVM. QueryInterface is handled by Java casts, and reference counting (Addref and Release) is handled by Java memory management.

In AddressBooksimpl and AddressBookimpl (I've appended "impl" to classes that implement my Automation interfaces), I use an anonymous inner class to obtain an implementation of Enumerable (which I need for the AutoEnum constructor); see Listing Seven. I could have let the implementation classes implement this interface, but that would add elements() to their public interfaces, which I preferred not to do.

AddressBookimpl and Addressimpl implement the Serializable interface, which declares no methods but allows those objects to be serialized. I've declared many of their private fields "transient," which prevents them from being saved or loaded. The identity of the containing AddressBooksimpl and Applicationimpl objects will change from time to time and should not be saved. This requires these classes to have methods to set proper values for these fields after the objects have been deserialized (loaded).

Both of my collection classes use standard Java collection classes to maintain their collections. The only job of the Addressimpl class (other than storing data) is to notify its parent AddressBook when any data has changed. The implementation classes for these are available electronically.

Handling Errors

The AutoEnum class provides several examples of error handling. Recall that all methods in the MIDL file were declared to return an HRESULT, which is not visible in the Java implementations. The way to set an HRESULT, along with other information such as a message string, is through a special exception class called ComException (actually, com.ms.com.ComException). This has two subclasses, ComFailException and ComSuccessException.

Listing Eight shows the AutoEnum.Skip method, which throws a ComSuccessException if there are no more entries. This results in the HRESULT being set to S_FALSE. This is not an error per se, but a special condition to which the client must be alerted. The definition of IEnumVARIANT calls for this code to be set in this case.

Listing Nine is the AutoEnum.Clone method. There is really no reasonable way to implement this method because Java Enumerations can't be cloned. To indicate this, it returns (throws) a standard error code, E_NOTIMPL.

In C++, this value would be defined in a standard header file. No such file exists for Java. Instead, I have defined it by declaring an enum called StdErrors in the MIDL file. JActiveX generates a Java interface for this enum, just as it does for a MIDL interface. The generated interface contains only constants (static final int variables). Listing Ten shows a portion of this enum.

Similarly, I have declared an enum called Errors; see Listing Eleven. These are application-specific error codes. The first entry sets the starting value for the sequence. This value indicates that these are error codes (the high bit), that they are interface specific (the 0x00040000 portion), and that they are not OLE standard codes (which are in the range below 0x80040200). You should always use this value for this purpose. Listing Twelve shows the AutoEnum.NextElement method, which uses one of these codes.

Using the API

The API needs to be entered in the registry to be usable, using the javareg command. Listing Thirteen shows a batch file that does this.

Any application or language that understands Automation can now use the API. The following files are available electronically:

  • testCreate.pl, a Perl script for creating an AddressBook file called MyBook.
  • testRead.pl, a Perl script that reads the file.
  • An Excel macro that reads the address book and populates the first two columns of a spreadsheet with names and phone numbers.

Conclusion

Sophisticated and finely tuned COM implementations may still require C++. Although Java's support for threading is excellent, Microsoft's support in Java for the various COM threading models is less so (or else largely undocumented). In addition, some COM interfaces (for example, the OLE DB database interfaces) require pointers to data buffers, which Java won't provide.

For most purposes, though, Java is a first-rate language in which to implement an Automation API. Java and COM Automation are valuable tools that work well together, and the techniques described here can help bridge disparate systems and solve real-world problems.

DDJ

Listing One

javareg /register /class:autoXTest /clsid:"{735640F0-3B59-11D1-B5B9-98596A000000}" /typelib:autoXTest.tlb /progid:"autoXTest.Main" /codebase:"g:\projects\autoX"

Back to Article

Listing Two

[    object,
    uuid(4f9dfa91-32ef-11d1-b5ac-9e4a44000000), dual,
    pointer_default(unique), helpstring("Application Interface")
]
interface Application : IDispatch
{
    /* for Application, this just returns pointer to self */
    [
        propget, helpstring("Application property")
    ]
    HRESULT Application([out,retval] Application** val);


</p>
    /* Parent, i.e. containing object for Application, returns pointer 
                                                               to itself */
    [
        propget, helpstring("Parent property")
    ]
    HRESULT Parent([out,retval] Application** val);


</p>
    /* name of the application */
    [
        propget, id(DISPID_VALUE), helpstring("Application Name")
    ]
    HRESULT Name([out,retval] BSTR* val);


</p>
    /* the collection of open AddressBooks */
    [
        propget, helpstring("AddressBooks property")
    ]
    HRESULT AddressBooks([out,retval] AddressBooks** val);
}

Back to Article

Listing Three

// Auto-generated using JActiveX.EXE 4.79.2207//   (JActiveX AddressBookLib.tlb)
// WARNING: Do not remove the comments that include "@com" directives.
// This source file must be compiled by a @com-aware compiler.
// If you are using the Microsoft Visual J++ compiler, you must use
// version 1.02.3920 or later. Previous versions will not issue an error
// but will not generate COM-enabled class files.


</p>
package addressbooklib;


</p>
import com.ms.com.*;
import com.ms.com.IUnknown;
import com.ms.com.Variant;


</p>
// Dual interface Application
/** @com.interface(iid=4F9DFA91-32EF-11D1-B5AC-9E4A44000000, 
                                              thread=AUTO, type=DUAL) */
public interface Application extends IUnknown
{
  /** @com.method(vtoffset=4, dispid=1610743808, type=PROPGET, 
                                   name="Application", addFlagsVtable=4)
      @com.parameters([iid=4F9DFA91-32EF-11D1-B5AC-9E4A44000000,
                                        thread=AUTO,type=DISPATCH] return) */
  public addressbooklib.Application getApplication();


</p>
  /** @com.method(vtoffset=5, dispid=1610743809, type=PROPGET, name="Parent", 
                                                 addFlagsVtable=4)
      @com.parameters([iid=4F9DFA91-32EF-11D1-B5AC-9E4A44000000,thread=AUTO,
                                             type=DISPATCH] return) */
  public addressbooklib.Application getParent();


</p>
  /** @com.method(vtoffset=6, dispid=0, type=PROPGET, name="Name", 
                                                        addFlagsVtable=4)
      @com.parameters([type=STRING] return) */
  public String getName();


</p>
  /** @com.method(vtoffset=7, dispid=1610743811, type=PROPGET, 
                                      name="AddressBooks", addFlagsVtable=4)
      @com.parameters([iid=4F9DFA96-32EF-11D1-B5AC-9E4A44000000,
                                        thread=AUTO,type=DISPATCH] return) */
  public addressbooklib.AddressBooks getAddressBooks();


</p>
  public static final com.ms.com._Guid iid = 
       new com.ms.com._Guid((int)0x4f9dfa91, (short)0x32ef, (short)0x11d1, 
       (byte)0xb5, (byte)0xac, (byte)0x9e, (byte)0x4a, (byte)0x44, (byte)0x0, 
       (byte)0x0, (byte)0x0);
}

Back to Article

Listing Four

    [        uuid(4f9dfa95-32ef-11d1-b5ac-9e4a44000000),
        appobject,  // the Application object
        helpstring("The Application Object for the AddressBook API")
    ]
    coclass CApplication
    {
        interface Application;
    };

Back to Article

Listing Five

[    object,
    uuid(4f9dfa92-32ef-11d1-b5ac-9e4a44000000),
    dual,
    pointer_default(unique),
    helpstring("AddressBook Interface")
]
interface AddressBook : IDispatch
{
    [
        propget, helpstring("Application property")
    ]
    HRESULT Application([out,retval] Application** val);


</p>
    [
        propget, helpstring("Parent property")
    ]
    HRESULT Parent([out,retval] AddressBooks** val);


</p>
    [
        propget, helpstring("The file name with path for this AddressBook")
    ]
    HRESULT FullName([out,retval] BSTR* val);


</p>
    [
      propget, helpstring("The file name (without path) for this AddressBook")
    ]
    HRESULT Name([out,retval] BSTR* val);


</p>
    [
      propget, helpstring("The path (without file name) for this AddressBook")
    ]
    HRESULT Path([out,retval] BSTR* val);


</p>
    [
        propget, 
        helpstring("Whether the file has been saved since the last changes")
    ]
    HRESULT Saved([out,retval] VARIANT_BOOL *val);


</p>
    [
        helpstring("close the Address Book")
    ]
    HRESULT Close();


</p>
    [
        helpstring("Save changes to Address Book")
    ]
    HRESULT Save();


</p>
    [
        propget, helpstring("Returns number of entries.")
    ]
    HRESULT Count([out, retval] long* retval);


</p>
    [
        propget, restricted, id(DISPID_NEWENUM),
        helpstring("returns an enumerator for the collection.")
    ]
    HRESULT _NewEnum([out, retval] IUnknown** ret); 


</p>
    [
        helpstring("Add a new Address entry.")
    ]
    HRESULT Add([in] BSTR name, [out,retval] Address** ret);


</p>
    [
        id(DISPID_VALUE), 
        helpstring("Given a name, returns the Address entry.")
    ]
    HRESULT Item([in] BSTR name, [out, retval] Address** ret);


</p>
    [
        helpstring("Remove an Address from the collection.")
    ]
    HRESULT Remove([in] BSTR name);


</p>
    [
        propget, helpstring("returns an enumerator for the collection.")
    ]
    HRESULT Enum([out, retval] DIEnum** ret);   


</p>
}

Back to Article

Listing Six

    public void Next(int n, Variant v[], int x[])    {
        int i;
        for(i = 0; i < n && enum.hasMoreElements(); ++i)
        {
            Variant var = new Variant(enum.nextElement());
            v[i] = var;
        }
        if(x != null)
            x[0] = i;
        if(i < n)
            /* returns S_FALSE status, indicating end of collection reached */
            throw new ComSuccessException();
    }

Back to Article

Listing Seven

    // get a COM enumeration object for this collection    private AutoEnum getAutoEnum()
    {
        /* use an anonymous class to implement the Enumerable
         * interface required by AutoEnum's constructor */
        return new AutoEnum(new Enumerable() {
            // get an Enumeration on the collection
            public Enumeration elements()

            {
                return entries.elements();
            }
        });
    }

Back to Article

Listing Eight

    public void Skip(int n)
    {
        while((n-- > 0) && enum.hasMoreElements())
            enum.nextElement();
        if(n > 0)
            // returns S_FALSE status, indicating end of collection reached
            throw new ComSuccessException();
    }

Back to Article

Listing Nine

    public IEnumVariant Clone()
    {
        throw new ComFailException( 
                         StdErrors.E_NOTIMPL, "Clone method not supported");
    }

Back to Article

Listing Ten

    enum StdErrors    {
        [helpstring("Unexpected failure")]
        E_UNEXPECTED = 0x8000FFFF,


</p>
        [helpstring("Not implemented")]
        E_NOTIMPL = 0x80004001,


</p>
        [helpstring("Ran out of memory")]
        E_OUTOFMEMORY = 0x8007000E,


</p>
        // etc.
    };

Back to Article

Listing Eleven

    enum Errors    {
        E_FIRST = 0x80040200,
    
        [helpstring("Index not valid for collection")]
        E_OUTOFBOUNDS,


</p>
        [helpstring("Specified Item not Found")]
        E_ITEMNOTFOUND,


</p>
        // etc.
    };

Back to Article

Listing Twelve

    public Object NextElement()    {
        if(enum.hasMoreElements())
            return enum.nextElement();
        else
            throw new ComFailException(
                        Errors.E_OUTOFBOUNDS, 
                        "Attempt to Retrieve after last element");
    }

Back to Article

Listing Thirteen

REM reg.cmdREM Register the Applicationimpl class as a COM object
REM /register means register the class
REM /class specifies which class
REM /clsid specifies the class id, which is the UUID of the coclass
REM /progid specifies a human-readable alias for the class id
javareg /register /class:Applicationimpl /clsid:"{4f9dfa95-32ef-11d1-b5ac-9e4a44000000}" /progid:"AddressBook.Application"


</p>

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.