Jawin Userguide - Calling a VTable Based COM Interface

Introduction

This part of the userguide, covers how to use Jawin when calling standard COM interfaces, where the functions are invoked through vtable entries. The information herein covers the "low-level" code, close to Jawin. This is the form of code the Jawin Type Browser generates - this generated code should be used instead of manually writing this "low-level" code. If the Jawin Type Browser is not able to generate complete stub-code for a COM interface, the guidelines in this document should be followed for manually writing the stub-code.

If you are working with a so called "scriptable" COM object - an object with a dispatch or dual interface - please see the documentation for dispatch based COM interfaces, as these are generally simpler to work with.

Content

  1. Quick 'n Dirty Overview of COM terms
    1. GUID, CLSID and IID
    2. ProgID, another way to specify a CLSID
  2. How to use Jawin to call an interface on a COM object
  3. Sample Code
    1. Extending org.jawin.COMPtr and registering the COM interface in Jawin
    2. Using the IDualTestItf-class
    3. Calling methods on the DualTest interface
  4. Error Handling
  5. Threading Issues
  6. Additional Resources

1. Quick 'n Dirty Overview of COM terms

The COM dictionary is full of cryptic terms and abbrevations that one must know a minimum of to use Jawin succesfully. A couple of these are explained in the sections below.

1.1. GUID, CLSID and IID

GUID is an abbrevation of Globally Unique Identifier, that is a unique 128-bit (16 bytes) integer used for identifying all kind of objects and interfaces in the COM world. Both CLSID's and IID's are instances of GUID's. A GUID is typically written as hex digits on the form {4 bytes - 2 bytes - 2 bytes - 2 bytes - 6 bytes}, eg. like {6EFEB125-55E2-4D6D-A17A-A2F038A647B2}.

A CLSID is a GUID identifying a COM class (a class id). Microsoft MSDN contains the following information about CLSID's. When creating a COM object use the CLSID to tell the COM libraries what type of object to create.

IID is an abbrevation of Interface Identifier. This is a GUID identifying a COM interface. It is important to distinguish between a COM interface and a COM class or object. One COM object typically implements several COM interfaces, so when using a COM object, one must both specify an identifier for the object to use (typically a CLSID or a ProgID, see later) and an identifier for the actual interface on the object (by specifying a IID). DIID is another abbrevation for interface identifer, and is used for interfaces implementing the IDispatch interface, i.e. a Dispatch Interface Identifier.

1.2. ProgID, another way to specify a CLSID

Since GUID's are hard to read (and remember) for most normal programmers, one can use a ProgID, or programmatic identifier instead of a CLSID for identifying a COM class. Microsoft MSDN contains the following information about ProgID's. ProgIDs are on the form <Program>.<Component>[.<Version>], eg. like CallCOMUnitTest.DualTest.1.

2. How to use Jawin to call an interface on a COM object

To use Jawin to call methods in an interface on a COM object, the class org.jawin.COMPtr should in broad terms be used the following way:

  1. The COMPtr-class should be extended and registered with Jawin for each requested COM interface on the COM object.
  2. When creating an instance of this class, the id for the COM object to create should be passed to the constructor for the object, either as a ProgID or as a CLSID.
  3. The methods on the COM interface can then be invoked by calling the comInvoke()-method on the created instance. Since this is a type-less method for generic calls, the recommended way to extend the COMPtr class, is, for each method in the COM interface, to make a delegator method, adding type-safety and hiding the complexity of calling the comInvoke() method (this is way the Jawin Type Browser generates stub code and makes it a lot easier for client code to use the class).

Note that COM requires that all threads calling a COM object must initialize the COM library. This is done by calling:

Ole32.CoInitialize();

After a thread is finished with all COM calls, it should call:

Ole32.CoUninitialize();

Microsoft MSDN documents the details of these two methods; CoInitializeEx and CoUninitialize.

It should be noted that COM has a special thread model. So if using COM objects in multithreaded applications, you unfortunately have to know at least the basic of this. So please see the section on Threading Issues if using COM objects from several threads.

When finished with the interface object, the close() method must be called to let COM decrease the reference count for the COM-object, and ultimately destroy the object. This call must happen on the same thread that created the interface object. And even though the COMPtr contains a finalize-method that tries to release the resources if close() was not explicitely called, this can not be relied on, since Java neither guarentees that finalize() will ever be called, nor is it guarenteed from which thread it will be called.

3. Sample Code

To show how to use a vtable based COM interface the following sections will be a code walk-through, using the IDualTest interface used in the Jawin unit-tests (this is supplied in bin/CallCOMUnitTestD.dll in the source-release. It is not included in the binary release). The properties and methods of the IDualTest can be found by inspecting the interface definition in cpp/CallCOMUnitTest/CallCOMUnitTest.idl.

Note that the IDualTest interface is actually a socalled dual-interface implementing the IDispatch interface. Therefore it is a lot easier to use this interface by following the guidelines in dispatch based COM interfaces. But as a matter of example we use the vtable based interface on this dual interface.

3.1. Extending org.jawin.COMPtr and registering the COM interface in Jawin

All used COM vtable based interfaces have to be registered in Jawin before use. The standard way to do this, is by making a new Java class for each used COM interface. This class should extend the COMPtr-class and follow the guide lines mentioned in the javadoc API documentation for the COMPtr class. This looks like the following for the IDualTest interface.

import org.jawin.COMPtr;
import org.jawin.GUID;
import org.jawin.IdentityManager;

/**
 * The class extends COMPtr.
 */
public class IDualTestItf extends COMPtr {

  /**
   * The Interface Identifier (IID) for the IDualTest interface.
   */
  public static final GUID IID = new GUID("{6EFEB125-55E2-4D6D-A17A-A2F038A647B2}");
  public static final int IID_TOKEN;
  static {
    // register the IID in Jawin on class loading time
    IID_TOKEN = IdentityManager.registerProxy(IID, IDualTestItf.class);
  }

  /**
   * Implementation of the required getIIDToken() method.
   */
  public int getIIDToken() {
    return IID_TOKEN;
  }

  /**
   * The required public no arg constructor.
   *
   * Important:Should never be used as this creates an uninitialized
   * IDualTestItf (it is required by Jawin for some internal working though).
   */
  public IDualTestItf() {
    super();
  }

  /**
   * For creating a new COM-object with the given progid and with
   * the IDualTest interface.
   *
   * @param progid the progid of the COM-object to create.
   */
  public IDualTestItf(String progid) throws COMException {
    super(progid, IID);
  }

  /**
   * For creating a new COM-object with the given clsid and with
   * the IDualTest interface.
   *
   * @param clsid the GUID of the COM-object to create.
   */
  public IDualTestItf(GUID clsid) throws COMException {
    super(clsid, IID);
  }

  /**
   * For getting the IDualTest interface on an existing COM-object.
   * This is an alternative to calling {@link #queryInterface(Class)}
   * on comObject.
   *
   * @param comObject the COM-object to get the IDualTest interface on.
   */
  public IDualTestItf(COMPtr comObject) throws COMException {
    super(comObject);
  }
}

3.2. Using the IDualTestItf-class

The first step before using the IDualTestItf-class, is to call Ole32.CoInitialize() like mentioned above. After the COM library has been initialized by this call, it is possible to create a COM object implementing the IDualTest interface, by using either the ProgID or CLSID constructor:

..
Ole32.CoInitialize();
// using the ProgID constructor IDualTestItf dualTestObj = new IDualTestItf("CallCOMUnitTest.DualTest");
// call methods on dualTestObj
..
// and finally release dualTestObj - this could be embedded in a finally clause
dualTestObj.close();
Ole32.CoUninitialize();
..

or using the CLSID constructor. Note that the CLSID is NOT the same as the IID registered in the IDualTestItf-class:

..
// using the CLSID constructor
GUID clsid = new GUID("{F420726D-905D-4D69-A225-A908152B2951}");
IDualTestItf dualTestObj = new IDualTestItf(clsid);
..

3.3. Calling methods on the DualTest interface

After one has gotten a reference to the wanted COM object the methods on the COM object is called by using one of the generic comInvoke() methods (the second one is the recommended method):

public byte[] comInvoke(int vtable, String instructions, int stackSize,
                        int argStreamSize, byte[] argStream, Object[] objectArgs)
                        throws COMException;

public byte[] comInvoke(int vtable, String instructions, int stackSize,
                        NakedByteStream argStream, Object[] objectArgs)
                        throws COMException;

The recipe for calling a native COM method is along the lines:

  1. Find the vtable (virtual function table or VTBL) index for the COM method. FIXME - insert reference of how do to this in Jawin Type Browser (vtable offset?) or/and with the OLE/COM object viewer/other tool. This should be used as the vtable argument in either of the comInvoke-methods.
  2. Serialize the arguments for the native method invocation onto a byte-array, and describe how the byte-array should be deserialized on the native side (this give values for the instructions-string, for the stackSize and for the argStream-parameters - don't worry if this seems unclear, further explanation follows below).
  3. Call the comInvoke method with the vtable-identifer and the serialized parameters for the native method. This will do the following on the native side:
    1. Lookup the correct COM function pointer from the vtable-identifier.
    2. Deserialize the argument stream into native parameter types, and push these parameters onto the stack for the function pointer.
    3. Invoke the function pointer.
    4. All the return and [out]-values from the function invocation are serialized onto a new byte-array and returned to the Java-side.
  4. Deserialize the returned byte-array into the Java-types and use the result and [out]-variables.

The two steps of this, that needs further explanation are the serialize and deserialize steps (i.e. step 2 and 4) together with the instruction string.

3.3.1. Serialize from Java-variables before calling comInvoke

Since Windows uses little-endian byte order internal, the serialization of Java variables onto the byte-array that should be passed to the native code, must use this byte order to. To assist in this, the caller should use the org.jawin.io.LittleEndianOutputStream- class. The recommended way to do this is like:

  1. Create a org.jawin.io.NakedByteStream-object, which is just a simple subclass to java.io.ByteArrayOutputStream, that allows access without copying to the wrapped byte array. This should be used for better performance. Note: be aware of the limitations of working with the internal byte-array, that is NEVER use the .length value of the internal array, instead use the value of the size() method on the NakedByteStream.
  2. Create a org.jawin.io.LittleEndianOutputStream object, passing the NakedByteStream-object to the constructor.
  3. Serialize all the [in]-variables to the byte array, by calling the appropriate methods on the LittleEndianOutputStream for each variable.

After this, the NakedByteStream contains the serialized variables in little-endian byte order, and can then be passed to the comInvoke() method.

A code snipped, showing an example of the above would be like

..
// first create the NakedByteStream
NakedByteStream nbs = new NakedByteStream();

// wrap it in a LittleEndianOutputStream
LittleEndianOutputStream leos = new LittleEndianOutputStream(nbs);

// then serialize two integers.
leos.writeInt(value1);
leos.writeInt(value2);
..

3.3.2. The instruction string for controlling native deserializing and serializing

For Jawin to be able to deserialize the byte array correctly on the native side, it needs meta-instructions for what the array contains. This is what the instructions and stackSize parameters are for.

TODO insert reference to a specification for the different instruction strings-values.

3.3.3. Deserialize into Java-variables after calling comInvoke

The comInvoke method returns any [retval] and [out] values in a new byte array. This should then be deserialized into Java types (just the opposite of the serializing in section 3.3.1) by using a org.jawin.io.LittleEndianInputStream like in this sample (with one integer [return] val, and one [out]-integer)

..
byte[] result = comPtr.comInvoke(..);

// wrap result in a LittleEndianInputStream
LittleEndianInputStream leis = new LittleEndianInputStream(new ByteArrayInputStream(result));

// any [retval] values are placed first
int retVal = leis.readInt();

// and then follows any [out] values
int outVal = leis.readInt();
..

4. Error Handling

The COM error model uses socalled HRESULT's as return values to indicate failure of a call. Jawin maps this into the Java Exception hierarchy by throwing a org.jawin.COMException on failure. This means that the user can program in standard Java-style, ie. catching exceptions and not having to worry about checking return HRESULT's.

Section 5 in the Jawin Architecture document contains some additional information about the conversion of HRESULT's into COMExceptions.

5. Threading Issues

If using COM references in multithreaded applications, you must make yourself comportable with how Jawin handles the threading issues that are present in COM. This is presented in section 6 in the Jawin Architecture document.

6. Additional Resources

Additional resources when working with vtable based COM objects from Jawin