ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Jawin, An Open Source Interoperability Solution

by Stuart Halloway
11/14/2001

The Java/Win32 integration project (Jawin) is a free, open source architecture for interoperation between Java and components exposed through Microsoft's Component Object Model (COM) or through Win32 Dynamic Link Libraries (DLLs). The current version of Jawin is 1.0.4, build 200111031306. You can download current source and binaries here.

You can use Jawin to interact with scriptable applications such as the Microsoft Office suite. You can use Jawin to call scriptable logic components such as Microsoft's COM-based XML parsers and tools. You can also use Jawin to access Win32 API features such as the Windows registry, security APIs, and the event log. In fact, Jawin allows your Java applications to call any legacy COM- and DLL-based code, without having to write any Java Native Interface (JNI) code. Using Jawin, you can call any component that can be scripted in the Windows environment. You can also call arbitrary COM components or DLL entry points.

Jawin includes a code generator for scriptable COM components. The code generator reads a type library and automatically emits the Java stubs needed to call the component. Jawin does not include a code generator for non-scriptable COM components or DLL entry points (yet).

This document should provide everything you need to get started using Jawin. If you have any questions, please subscribe and post them to the Jawin mailing list

Contents

  1. Why Jawin? (JNI Is Not Enough)
  2. Installing Jawin
  3. Calling a Scriptable COM component
  4. Calling a Scriptable COM component using generated stubs
  5. Using JawinGen to generate stubs
  6. Calling a DLL entry point
  7. Jawin Architecture
    1. Intrinsic Functions
    2. Shared Stubs
    3. Generic Stub
    4. Error Handling
    5. COM threading
  8. Troubleshooting Jawin
  9. Resources

Why Jawin? (JNI is Not Enough)

The Java Native Interface (JNI) is a standard way to access native code from the Java platform. JNI provides a set of low-level primitives that can be used to marshal arguments from Java into native code, and vice versa.

Note the choice of the word "primitive." JNI is a very low-level approach to calling native code. Examine the code fragment below, which shows using JNI to access the registry. Only the code in bold is relevant. All of the rest of the code is devoted to managing parameters and resources, and is totally irrelevant to the task at hand.

JNIEXPORT jint JNICALL Java_NTRegistry_getDword
(JNIEnv *pEnv, jclass cls, jint hive, jstring key, jstring name)
{
  unsigned long dw = -1;
  unsigned long size;
  HKEY hkey = NULL; jchar* psName = NULL;
  jchar* psKey = NULL;
  if (!getString(pEnv, key, &psKey)) goto fail;
  if (!getString(pEnv, name, &psName)) goto fail;
  if (ERROR_SUCCESS != ::RegOpenKeyW((HKEY)hive, psKey, &hkey)) goto fail;
  size = sizeof(dw);
  if (ERROR_SUCCESS == ::RegQueryValueExW(hkey, psName, NULL, NULL, 
                       (BYTE*) &dw, &size)) goto cleanup;
fail:
  cls = pEnv->FindClass("java/lang/IllegalArgumentException");
  if (cls) pEnv->ThrowNew(cls, "Could not read registry key");
cleanup:
  if (hkey) ::RegCloseKey(hkey);
  if (psKey) free (psKey);
  if (psName) free (psName);
  return dw;
}

This is not the intended use for JNI. JNI is best when it is used to build a higher-level marshalling layer that provides a transparent, or near transparent, mapping between Java objects and objects on a particular native platform. In order to access the very large number of COM components and Win32 DLLs, several companies and open source projects have leveraged JNI to create more developer-friendly, Win32-specific solutions.

Jawin is one such project. I built Jawin because I was dissatisfied with JNI as a mechanism for interop.  JNI requires that you write custom native code each time you want to add a new capability.  With Jawin, all the native code is in Jawin.dll, and no additional native code needs to be written.

Installing Jawin

To install Jawin, download and unzip the file http://staff.develop.com/halloway/code/jawin.zip to a directory of your choice. After you have unzipped the Jawin code, you should test to make sure that Jawin is working correctly. You can do this by using Jawin's Ant build script.

Jawin is built using Ant, an open source Java build tool. In addition to providing support for compiling Java code, Ant also makes it easy to launch most java command-line tools, including java itself. Jawin's Ant build manifest is the file build.xml in the root directory. If you are an experienced Ant user, you can learn a lot about Jawin just by studying this file. To verify that Jawin is working correctly, install Ant and execute the following Ant task:

  ant -emacs "run tests"

You should see a spate of "Test succeeded" methods and no exception traces. If you do see an exception trace, Jawin is not functioning correctly in your machine. Please post your hardware and software configuration, plus the full stack trace, to the Jawin mailing list.

Calling a Scriptable COM component

Probably the most common use of Jawin is to call a scriptable COM component. The code fragment below demonstrates using Jawin to instantiate PowerPoint and build a very simple slide show.

    try {
  Ole32.CoInitialize();
  DispatchPtr app = new DispatchPtr("new:PowerPoint.Application");
  app.put("Visible", true);
  DispatchPtr preses = app.getObject("Presentations");
  DispatchPtr pres = (DispatchPtr) preses.invoke("add", -1);
  DispatchPtr slides = pres.getObject("Slides");
  DispatchPtr slide = (DispatchPtr) slides.invoke("Add", 1, 2);
  DispatchPtr shapes = slide.getObject("Shapes");
  DispatchPtr shape = (DispatchPtr) shapes.invoke("Item", 1);
  DispatchPtr frame = shape.getObject("TextFrame");
  DispatchPtr range = frame.getObject("TextRange");
  range.put("Text", "Using Jawin to call COM objects");
  Ole32.CoUninitialize();
    }
    catch (Exception e) {
  e.printStackTrace();
    }

You can execute this demo with the command line

  ant -emacs "ppt demo"

The calls to Ole32.CoInitialize and Ole32.CoUninitialize set up and tear down the COM libraries on the current thread. You must make these calls on any thread that will be calling COM objects. The first DispatchPtr is created directly, using the new moniker followed by the ProgId for the COM class you want to use. (The name DispatchPtr comes from the fact that scriptable COM components implement the IDispatch interface.) Subsequent DispatchPtr instances are created by calling methods and accessors, working down the object hierarchy to finally place some text on a slide.

This code has one noteworthy advantage: it is simple. Code written in this style requires no compile-time knowledge of the COM component you are calling. You could attempt to invoke, put, or get any name you want -- the Java compiler will not care. 

This simplicity can also be seen as a disadvantage. Because it uses no type information to describe PowerPoint, the compiler will not tell you if this code represents a valid set of method calls and property accesses. Instead of always using DispatchPtr, it would be nice to have actual Java classes named Application, Presentation, Slide, etc., with specific methods that can be verified by the Java compiler.

Jawin's code generation feature can create these helper classes, which are called stubs.

Calling a Scriptable COM Component Using Generated Stubs

The following code fragment is identical in effect to the code sample above, except it uses generated java classes as stubs for the dispatch interfaces in the type library.

try {
  Ole32.CoInitialize();
           _Application app = new _Application("new:PowerPoint.Application");
  app.put("Visible", true);
           Presentations preses = app.getPresentations();
           _Presentation pres = preses.Add(-1);
           Slides slides = pres.getSlides();
           _Slide slide = slides.Add(1,2);
           Shapes shapes = slide.getShapes();
           Shape shape = shapes.Item(new Integer(1));
           TextFrame frame = shape.getTextFrame();
           TextRange range = frame.getTextRange();
           range.setText("Jawin to call COM objects");
           Ole32.CoUninitialize();
    }
    catch (Exception e) {
           e.printStackTrace();
    }

You can run this example using the Ant command line

ant -emacs "ppt stubs demo"

When using the generated stubs, you must still call Ole32.CoInitialize and Ole32.CoUninitialize in order to bootstrap the COM libraries. However, this code has the notable advantage of being strongly typed. In addition to strongly-typed references, the methods exposed by the stubs contain strongly-typed parameter lists. Using the generated stubs, you get compile-time protection against type misuse.

Jawin also provides a mechanism for generating a second type of file: interfaces which represent any COM enums found in the type library. These interfaces scope a list of named constants inside a namespace which matches the enum name from the library. This allows the Java developers the same ability to avoid "magic number" programming that is afforded to COM programmers using VB or C++.

Using JawinGen to Generate Stubs

The stub generator used by Jawin, jawingen/jawinGenUI.exe, has both a windowed and a command-line interface. Creating the stubs is a simple process. You need only provide four pieces of information: the full path to the type library you wish to parse, a destination folder to place the output, the full package name you wish the stubs to be a part of, and the project name you wish to use to identify the output. You can provide this information through the Windows interface, or via flags on the command line. If you provide a subset of these items via the command line, the windowed interface will be invoked and filled in with the information provided. If all four items are given via the command line, JawinGen will run in "silent" mode.

For example, the following command line will generate the stubs for PowerPoint (assuming the directory locations are the same on your machine):

jawinGen -l '/Program Files/Microsoft Office/Office/msppt9.olb' 
                  -d ../../stubs/ppt -p jawin.ms.ppt -j MSPPt 

After a successful run, JawinGen will produce a Java stub class for each Dispatch interface in the type library and an interface file for each enum. In addition, it will produce an Ant buildfile at the root of the destination directory. To compile your stubs and interfaces, use the command line "ant" from the root of your output directory. The .java files and interfaces will all be compiled and JARred.

NOTE: JawinGen is currently an alpha release. Please send feedback to the Jawin mailing list or directly to justin@gehtland.com.

Calling a DLL Entry Point

In many ways, calling a DLL entry point is similar to calling a scriptable COM object. As you recall, the class DispatchPtr represented a scriptable COM object. The class FuncPtr represents a DLL entry point, and you create a FuncPtr by specifying a library name and entry point name. The following snippet gets the MessageBoxW method and uses it to show a message box.

//ant -emacs "hello dll"
      FuncPtr msgBox = new FuncPtr("USER32.DLL", "MessageBoxW");
      msgBox.invoke(0, "Hello From a DLL", "From Jawin", 0, ReturnFlags.FAIL_ON_FALSE);

The first four arguments to invoke are the arguments expected by MessageBox: int, String, String, and int. FuncPtr has several different overloaded forms for common argument types. The final argument is a ReturnFlags that tells Jawin how to process the return value. DLL entry points have several different ways to indicate errors. Some return false (0) on failure, others return false on success. Still others return a COM HRESULT, or indicate failure through a call to the helper function GetLastError.

To call MessageBoxW using a FuncPtr, you have to know the argument types in advance, and you have to know how MessageBoxW indicates failure. What happens if you pass the wrong arguments? The most likely outcome is that your program will crash.  If you are familiar with Visual Basic, this problem is equivalent to using the wrong Declare statement. An API that crashes on bad input is less than ideal, so Jawin also includes strongly-typed stubs that hide the details of calling invoke behind a type-safe wrapper. Using the MessageBoxW stub, the above code would be:

//ant -emacs "hello dll stub"
User32.MessageBoxW("Hello From a DLL Stub", "Jawin");

This is much safer, since there are no overloaded forms of User32.MessageBoxW that might take the wrong arguments and crash. At this time, Jawin does not include an automatic stub generator for DLL entry points; volunteers code the stubs by hand. If you have an API that you need wrapped in a stub, post a description to the mailing list.

Jawin Architecture

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:

  • Type information that describes the entry points or interfaces to be accessed.

  • Intrinsic functions (helper functions that marshal particular data types).

  • A marshalling layer that assembles calls to the intrinsic functions based on the type information.

Here is how Jawin does it (click to view diagram).

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.

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 com.develop.io package and the com.develop.jawin.Variant class. LittleEndianInputStream and LittleEndianOutputStream know how to handle Java primitives, e.g.:

package com.develop.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_NOTHING 0
#define CHECK_FOR_FALSE 1
#define CHECK_FOR_FAILED_HR 2
inline bool checkRet(int ret, int flags) {
  switch (flags) {
    case CHECK_NOTHING:
      return true;
    case CHECK_FOR_FALSE:
      if (!ret) {
        JNIComException::SetLastError();
        return false;
      }
      return true;
    case CHECK_FOR_FAILED_HR:
      if (FAILED(ret)) {
        JNIComException::SetContextException(ret);
        return false;
      }
      return true;
      //etc.
  }
}

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.

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 com.develop.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.

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 here. 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.

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 with it the type information.

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.

Diagram.

The code "kGGk" is the instruction stream that tells how to rebuild the call stack. The "k" 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.

Error Handling

Jawin automatically converts COM and Win32 errors into instances of com.develop.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 -emacs "demo bad HRESULT"
> Got Expected Error: com.develop.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:

com.develop.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.

COM Threading

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:

  • A component can live in a single-threaded apartment (STA), which means that it should always be called from the same thread. 
  • 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. 
  • Components can also choose the "Both" threading model, which means that they will belong to the apartment of the thread that creates them.
  • 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."
  • 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 Tim Ewald, "Transactional COM+: Building Scalable Applications", Addison-Wesley, Reading, MA, 2001.) 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: 

  • For Java programmers who are not experts on COM apartments, the details of COM threading are hidden as much as is feasible.
  • 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:

  • 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.

  • If you wish to use a COMPtr, DispatchPtr, or subclass from more than one thread, you should call IdentityManager.createGITRef 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 imposed by Jawin on each method call. 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.

  • You can improve the performance of the second option by calling IdentityManager.createDirectRef 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. 

Troubleshooting Jawin

If you are getting an UnsatisfiedLinkError when loading Jawin, read the section Loading Jawin Native Code. If some stubs or data types cause exceptions, you may be able to track the problem using Jawin's Diagnostic Output. If this documentation does not solve the problem, post your question to the mailing list

Loading Jawin Native Code

When you use Jawin to access native code, you need only load the single Jawin library. No other native code is necessary. However, Jawin itself is a standard JNI library. The first challenge in using Jawin is making sure that the Jawin library is visible to your application. You can set the standard java.library.path property to point to the location of jawin.dll on your system:

    java -Djava.library.path=d:/jawin/bin SomeMainClass

Jawin also includes a special flag that you can use to specify the full path and name of the Jawin library. This form is usually used to load the debug version of the Jawin native code, like so:

    java -Dcom.develop.jawin.hardlib=d:/jawin/bin/jawind.dll SomeMainClass

The debug version of Jawin is located at bin/jawind.dll in your Jawin installation. The purpose of the debug build is to help troubleshoot problems inside Jawin. The debug version of Jawin contains debug symbols and may sometimes ASSERT before throwing a COMException back to Java. This is by design, so that you can jump into the debugger to see what caused the exception. You should never deploy the debug version of Jawin.

You can also access the Jawin library by adding it to your Windows system directory, or by running your application from the directory where the library is located. These methods may seem simpler, but they can cause configuration headaches later. The explicit command-line flags listed above are the preferred way to locate the Jawin native library.

Diagnostic Output

Jawin includes several auditing flags that can be used to emit diagnostic output, as described in the table below. The diagnostic output will be enabled if these flags are set to any value.

Flag Name Diagnostic Output
com.develop.jawin.traceRefs COM object reference counting
com.develop.jawin.traceWin32 Win32 marshal packets
com.develop.jawin.traceCom COM marshal packets
com.develop.jawin.traceDispatch Scriptable COM object marshal packets

This listing shows the diagnostic output from running the PowerPoint demo with com.develop.jawin.traceDispatch enabled. As you can see, the information is extensive, including a binary log of all data transmitted between Java and native code.

Resources

This introduction to Jawin assumes that a reader is already somewhat familiar with COM, Win32, and Java.

  • For COM platform basics, see Don Box, Essential COM, Addison-Wesley, Reading, MS 1998.
  • For Win32, see Programming Applications for Microsoft Windows, 4th Ed., Microsoft Press, Redmond, WA, 1999.
  • For Java platform basics, see Stuart Halloway, Component Development for the Java Platform, Addison-Wesley, Reading, MA, 2001, especially the appendix on Jawin.
  • Jawin is one of a large number of open-source and commerical products that integrate Java with COM and Win32. For a list of alternatives, see Java/Win32/COM interoperation product list.

Please send comments and questions to the Jawin mailing list. You can subscribe on the Web at http://discuss.develop.com/jawin.html.

Stuart Halloway is a co-founder and CEO of Relevance, Inc. Relevance provides development, consulting, and training services based around agile methods and leading-edge technologies such as Ruby and Clojure.


Return to ONJava.com.