Jawin Architecture

Introduction

This document gives an overview of the Jawin Architecture. For most users only the sections about Error Handling and COM Threading contains "need to know" information.

Content

  1. Architectural Overview
  2. Jawin Intrinsics
  3. Shared Stubs
  4. Jawin Generic Stub
  5. Error Handling
  6. COM Threading
    1. Initializing the COM Library on each Thread
    2. Thread Local and Thread Neutral References (the Apartment Nightmare)

1. Architectural Overview

One goal of any interop tool is transparency. Ideally, a Java programmer could use a COM or Win32 component without even knowing she was doing so. The component would behave just like any ordinary Java class, and the presence of Win32/COM would be entirely transparent to the programmer.

In reality, some details cannot be entirely hidden, so the next best thing is to have translucent access. A translucent stub is a Java class that hides most of the details of COM and Win32. The code below shows translucent stub code for accessing the Windows registry from Java. In this example. the methods have simple signatures that do not cause any particular challenges for a marshalling layer. As a result, the only departure from transparency is the presence of COMExceptions.

public class Registry {
  public static int OpenKey(int key, String subkey) throws COMException;
  public static int CreateKey(int key, String subkey) throws COMException;
  public static void DeleteKey(int key, String subkey) throws COMException;
  public static String QueryStringValue(int key, String subkey) throws COMException;
  public static byte[] RawQueryValue(int key, String subkey) throws COMException;
  public static void CloseKey(int key) throws COMException;
}

In order to produce stubs like the one shown above, you need three things:

  1. type information that describes the entry points or interfaces to be accessed.
  2. intrinsic functions (helper functions that marshal particular data types).
  3. a marshalling layer that assembles calls to the intrinsic functions based on the type information.

Here is how Jawin does it: Figure 1: Jawin overview The translucent stub is generated from type information, and is different for each COM interface or DLL entry point. Shared stubs handle common method signatures by calling native methods with correlated JNI signatures. The generic stub handles "everything else", i.e. methods that have less common signatures and therefore no shared stub. Both the generic stub and shared stubs use helper functions called intrinsic functions to marshal particular data types.

2. Jawin Intrinsics

Jawin intrinsic functions are the atoms of marshalling. An intrinsic function knows how to convert one or more data types into a wire format, or how to retrieve data types from a wire format.

On the Java side of Jawin, the intrinsics are located in the org.jawin.io package and the org.jawin.Variant class. LittleEndianInputStream and LittleEndianOutputStream know how to handle Java primitives, e.g.

package org.jawin.io;
public class LittleEndianInputStream {
  public final int readUnsignedShort() throws IOException {
    InputStream in = this.in;
    int ch2 = in.read();
    int ch1 = in.read();
    if ((ch1 | ch2) < 0)
      throw new EOFException();
    return (ch1 << 8) + (ch2 << 0);
  }
//etc.
}

As the class names suggest, Jawin marshalling always uses little endian byte order, which is the ordering expected by Win32 and COM. This strategy could be called "Java makes right" since all the byte-ordering work is done in Java, both during method calls and returns. I chose this approach because I believe that Java code is easier to write and test and equivalent COM code.

In addition to handling data types, intrinsic functions also handle other semantic conversions. For example, there is a native intrinsic function that converts Win32/COM return values into Java exceptions:

#define CHECK_NONE 0
#define CHECK_FALSE 1
#define CHECK_HRESULT 2
#define CHECK_W32 3

inline bool checkRet(int ret, int flags) {
  switch (flags) {
  case CHECK_NONE:
    return true;
  case CHECK_FALSE:
    if (!ret) {
      JNIComException::SetLastError();
      return false;
    }
    return true;
  case CHECK_HRESULT:
    if (FAILED(ret)) {
      JNIComException::SetContextException(ret);
      return false;
    }
    return true;
  case CHECK_W32:
    if (ret != ERROR_SUCCESS) {
      JNIComException::SetContextException(ret);
      return false;
    }
    return true;
  default:
    JNIComException::SetContextException("Invalid code in checkRet");
    return false;
  }
}

On the native side of Jawin, the intrinsic functions are spread across SharedStubs.cpp, GenericStub.cpp, and Transform.cpp. At some future date they may be factored into an Intrinsics.cpp.

3. Shared Stubs

Jawin uses shared stubs to marshal COM and Win32 methods that have common signatures. For example, consider the following Win32 function calls:

HGDIOBJ GetStockObject(int fnObject);
HRESULT CoInitialize(LPVOID reserved);
BOOL UpdateWindow(HWND hwnd);
BOOL DeregisterEventSource(HANDLE hEventLog); 

Semantically, the argument types and return values of these methods are all very different. However, all these types marshal the same, as 32-bit values. Thus it is possible to implement all of these methods with a single JNI entry point:

public static native int invokeI_I(int arg0, int func, int flags);

This greatly reduces the number of entry points that must be implemented to handle a given set of APIs. Instead of one entry point per method, you need only one entry point per unique signature. Given this "shared stub" an implementation of CoInitialize looks like this:

public static void CoInitialize() throws COMException {
  FuncPtr fp = new FuncPtr("OLE32.DLL", "CoInitialize");
  fp.invoke(0, ReturnFlags.CHECK_HRESULT);
}

Jawin's shared stubs are in the Java class org.jawin.marshal.SharedStubs. The native implementation is in the files SharedStubs.cpp and COMMarshal.cpp.

Of course, not all functions can be implemented with a finite number of shared stubs. For methods with more exotic signatures, Jawin also provides a generic stub.

4. Jawin Generic Stub

Jawin's generic stub is a true marshaller, similar to RMI or DCOM. The generic stub views a function call as a sequence of events, as shown in the figure 2 below. Intrinsic functions serialize a function into a request message, and the generic stub moves this message into native space. There, another set of intrinsic functions convert the serialized request into a call stack and invoke the function. The entire sequence plays backwards to serialize a response message with return values or exceptions and ship it back to the caller. Figure 2: Jawin generic stub The generic stub knows how to send messages back and forth from Java to Win32, but it does not know the specifics of any particular method. On the Java side, the translucent stub knows these details. However, there is no Win32-side equivalent of the translucent stub. Sticking to the RMI and CORBA naming convention, such a component would be called a skeleton. Jawin does not use a skeleton because the request message carries the type information with it.

In addition to the serialized request, the generic stub also passes an instruction stream that describes how to deserialize the request on the Win32/COM side. The instruction stream is sequence of bytecodes that is processed by a simple interpreter to rebuild the call stack. These are not bytecodes in the sense of a Java binary class; they are Jawin specific and arbitrary.

The figure below shows the instruction stream that is generated when marshalling a call to the Win32 API MessageBoxW. The instruction stream begins with "0,0,0,0" for the first argument, which happens to be an integer valued zero. The second argument is a string, which is a little more complex. The "5,0,0,0" is the little endian representation of the string length, followed by the string's contents encoded as Unicode characters. The remainder of the request (not shown in the figure) is generated in similar fashion. Figure 3: Instruction stream The code "IGGI" is the instruction stream that tells how to rebuild the call stack. The "I" code indicates that a 32 bit value should be copied directly into the stack. The "G" code indicates that the stub should allocate a Unicode string, read the stream's contents into the string, and then place a pointer to the string on the call stack. These instruction codes are only a sample, Jawin's instruction string vocabulary supports several stack transformation more complex than those shown here. The instruction strings drive a state machine that is implemented as a switch statement in the Transform::process function.

Jawin's generic stub is primitive compared to RMI or DCOM. For example, it does not understand pointer aliasing. Improvements are being made the generic stub on an as-needed basis to support particular use cases.

(Jan 2002) I am working to document the instruction streams here.

5. Error Handling

Jawin automatically converts COM and Win32 errors into instances of org.jawin.COMException.

For COM errors, the exception will include the HRESULT and the error string. For example, the following fragment attempts to create a non-existent COM component:

// from demos/src/demos/BadHresult.java
try {
  Ole32.CoInitialize();
  DispatchPtr p = new DispatchPtr("new:Nonexistent.Component");
  throw new Error("Attempt to create nonexistent component should fail");
} catch (COMException e) {
  System.out.println("Got Expected Error: " + e);
}

The preceding example will produce the following COMException:

> ant "demo bad HRESULT"
> Got Expected Error: org.jawin.COMException: 800401e4: Invalid syntax

Often, the HRESULT is inadequate to diagnose an error. In such situations, it is necessary to also collect any additional thread-specific error information set by the object. As an example of this, consider the ADO demo included with Jawin (see demos/src/jawin/ado). The Ant build target "ado demo" passes in the data source name "DSN=Pubs". If this data source name does not exist, you will see an error like this one:

org.jawin.COMException: 80020009: 
[Microsoft][ODBC Driver Manager] Data source name not found and no default
    driver specified
[src=Microsoft OLE DB Provider for ODBC Drivers,
    guid={0C733A8B-2A1C-11CE-ADE5-00AA0044773D}]

Without the additional information provided by SetErrorInfo, you would see only the HRESULT and the generic information Exception Occurred.

For Win32 errors, the COMException will include the error code and text reported by calling GetLastError.

6. COM Threading

The issues in this section only exists for COM, and not for Win32/DLL programming.

6.1. Initializing the COM Library on each Thread

First, COM requires that all threads calling a COM object must initialize the COM library before making any COM calls. This is done by calling:

Ole32.CoInitialize();

If a thread shares or uses COM objects from other threads, the COM library should be initialized as multithreaded instead of as the default single threaded. This is done by passing the COINIT.MULTITHREADED parameter to the CoInitialize method.

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.

6.2. Thread Local and Thread Neutral References (the Apartment Nightmare)

Unlike Java or Win32, COM provides some built-in protection for components that can only be called safely from certain threads. The architecture to support this is based around apartments. An apartment is a group of running components and threads with similar threading characteristics. The relationship between a COM component and the apartment model is specified at deployment by setting well-known values in the registry, but can also be modified at runtime by implementing the IMarshal interface. There are a confusing variety of possibilities:

  1. A component can live in a single-threaded apartment (STA), which means that it should always be called from the same thread.
  2. A component can live in the multi-threaded apartment (MTA), which means that it can be called from any thread in the MTA pool, but can never be called from an STA thread.
  3. Components can also choose the "Both" threading model, which means that they will be belong to the apartment of the thread that creates them.
  4. Components can implement the IMarshal interface to further customize threading behavior at runtime, adding additional nuances to the apartment behavior suggested by their registry settings. This feature is commonly used to create components that can visit any apartment for the duration of a method call. Such components may be called "agile" or be said to "aggregate the free-threaded marshaller."
  5. In MTS and COM+, the threading model is extended to also include the notion of context. A context is a subspace of a process that provides some service at runtime, such as security checking or transaction enlistment. Components may belong to the same apartment but still be incompatible if they belong to different contexts.

If you are a Java programmer, and you think that the preceding section sounds complex, bewildering, and likely to cause trouble, you are right! (For the full story on apartments and context, see [Ewa01].) To summarize the apartment story: Apartments are complex, and if you call a COM apartment from the wrong thread you may violate apartment rules and cause bizarre failures far removed from the problem point in the code.

The Jawin architecture provides two levels of service for Java programmers:

  1. For Java programmers who are not experts on COM apartments, the details of COM threading are hidden as much as is feasible.
  2. For Java programmers who are expert on COM apartments, full functionality should be available.

The Jawin architecture accomplishes these objectives by mandating the following programming model:

  1. When you create or otherwise gain access to a COMPtr, DispatchPtr, or any subclass, you should only use that pointer from the thread you are on. When you are done, you must call the close method, which will release the underlying IUnknown*. This approach is recommended for anyone calling COM objects from a single thread.
  2. If you wish to use a COMPtr, DispatchPtr, or subclass from more than one thread, you should call the createGITRef()-method on the COMPtr. This call should be made on the original thread, to create a context-neutral reference in the Global Interface Table. After you do this, you can use the newly created reference from any thread, and Jawin will automatically hide the details of creating a local IUnknown* as needed. While this approach will always work, it significantly increases the overhead (by a factor 5-20) imposed by Jawin on each method call.
    Note that creating such a GIT-reference increases the native reference count, so both references must be closed as usual. The original thread-local reference must be closed last, and on the original thread. An example of creating such a thread-neutral reference are like:

    ..
    // create a standard thread local reference
    DispatchPtr directDisp = new DispatchPtr(CLSID);
    // get the thread neutral GIT reference
    DispatchPtr gitDisp = (DispatchPtr)directDisp.createGITRef();
    // and now we are ready to use the gitDisp-reference from several threads.
    ..
    ..
    // finally after usage from multiple threads the two references
    // must be closed on the original thread.
    gitDisp.close();
    directDisp.close();

    This approach is recommended for Java programmers who plan to use the same COM object from multiple threads, and are not comfortable with the details of COM apartments.
  3. You can improved the performance of option 2 by calling the createDirectRef()-method on the thread-neutral reference to create a thread-local reference just before making a series of method calls. This direct reference must be closed on the thread where it was created. This option is recommended for Java programmers who are comfortable with the details of COM apartments.