The JACOB Project
 

 

The JACOB Project: A JAva-COM Bridge

Now you can call COM Automation from any Win32 Java VM using JNI

Copyright 1999-2004 Dan Adler

Latest Version: October 17, 2004 (V1.8)


John Timney included a chapter about JACOB in the book: Professional JSP By Wrox Publishing July 26, 2000.

Rich Katz mentioned JACOB in an article in .

gave us 4 stars! gave us 3.5 stars.

What Is JACOB?

JACOB is a JAVA-COM Bridge that allows you to call COM Automation components from Java. It uses JNI to make native calls into the COM and Win32 libraries. The JACOB project started in 1999 and is being actively used by thousands of developers worldwide. As an open-source project, it has benefitted from the combined experience of these users, many of whom have made modifications to the code and submitted them back for inclusion in the project.

The JACOB project has moved to Sourceforge.net. Verion 1.14.3 is now available at Sourceforge. If you are a sourceforge developer and are interested in contributing to the project, please contact the project administrators. The rest of this page is OUT OF DATE - please go to Sourceforge for current information and support.

Version 1.7 includes some new features as well as many bug fixes, memory leak plugs, as well as new models for threading and object lifetime which will make the code much more robust. See the release notes below for details.

Here is the background of how the project got started.

Release Notes and Credits

Version 1.7 Release Notes and Credits

Version 1.6 and Previous Versions Release Notes and Credits

Related Links

Downloads

The JACOB binary distribution (jacobBin_XX.zip) includes:

  1. jacob.jar: a JAR file for the java classes which you must add to your CLASSPATH. The package names replace com.ms with com.jacob (for example com.ms.com.Variant maps to com.jacob.com.Variant.
  2. jacob.dll: a small Win32 DLL which you must add to your PATH.
  3. samples: provided in Java source and compiled form to demonstrate various features of the product. In particular, a set of wrapper classes for Microsoft® ADO are provided as samples.

The source code is available in the JACOB source distribution (jacobSrc_XX.zip), which includes both the Java and C++ code. The source distribution is a superset of the binary one, so you don't need both.

Version 1.7

Download jacobBin_17.zip       Download jacobSrc_17.zip

Version 1.6

Download jacobBin_16.zip       Download jacobSrc_16.zip

License

This is the old JACOB License. The new license is at Sourceforge (see link above).

Usage and Documentation

Since the com.jacob classes are intended to be compatible with the Microsoft® com.ms classes, it is recommended that you download the documentation of Microsoft's SDK for Java. To find documentation on the com.ms.com package, go to: http://www.microsoft.com/java/download/dl_sdk40.htm and at the bottom of the page is a link that says: Microsoft SDK for Java 4.0 Documentation Only. You should download that file and install it. Then, view sdkdocs.chm and look for "Microsoft Packages Reference". Hopefully, the next release of JACOB will include full javadoc (volunteers?)...

The following example uses Microsoft® Excel as an Automation server:


import com.ms.com.*;
import com.ms.activeX.*;

public class DispatchTest
{
  public static void main(String[] args)
  {
    ActiveXComponent xl = new ActiveXComponent("Excel.Application");
    Object xlo = xl.getObject();
    try {
      System.out.println("version="+xl.getProperty("Version"));
      System.out.println("version="+Dispatch.get(xlo, "Version"));
      xl.setProperty("Visible", new Variant(true));
      Object workbooks = xl.getProperty("Workbooks").toDispatch();
      Object workbook = Dispatch.get(workbooks,"Add").toDispatch();
      Object sheet = Dispatch.get(workbook,"ActiveSheet").toDispatch();
      Object a1 = Dispatch.invoke(sheet, "Range", Dispatch.Get,
                                  new Object[] {"A1"},
                                  new int[1]).toDispatch();
      Object a2 = Dispatch.invoke(sheet, "Range", Dispatch.Get,
                                  new Object[] {"A2"},
                                  new int[1]).toDispatch();
      Dispatch.put(a1, "Value", "123.456");
      Dispatch.put(a2, "Formula", "=A1*2");
      System.out.println("a1 from excel:"+Dispatch.get(a1, "Value"));
      System.out.println("a2 from excel:"+Dispatch.get(a2, "Value"));
      Variant f = new Variant(false);
      Dispatch.call(workbook, "Close", f);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      xl.invoke("Quit", new Variant[] {});
    }
  }
}

As it stands, the code will only compile with JVC (Microsoft's compiler) and only run under JVIEW (Microsoft's VM). However, if you have the JACOB distribution installed, then you can replace the two top lines with:


import com.jacob.com.*;
import com.jacob.activeX.*;


and now, you can compile this with any Java compiler and run it using any Java VM on any Win32 platform.

Frequently Asked Questions (FAQ)

Massimiliano Bigatti has compiled The JACOB FAQ based on his experience and the discussions in the JACOB yahoo group: http://groups.yahoo.com/group/jacob-project.

Implementation

The JACOB implementation of a COM Automation controller is quite different than the one used in the Microsoft® JVM . In JACOB, the com.jacob.com.Dispatch class is the basic building block. This class is used to create an instance of an Automation server. The com.jacob.activeX.ActiveXComponent class simply extends the Dispatch class to provide compatibility with the way Automation servers are created in the MS JVM. The Dispatch class's methods are all static for compatibility with the MS class.

The com.jacob.com.Dispatch constructor takes a String as an argument. If the string contains a colon (':'), then I try to interpret it as a Moniker, otherwise, I assume it's a ProgID. This means that if you want to create an object by its CLSID instead of ProgID, then you should use the clsid: Moniker as the string. I find this cleaner than manipulating GUID classes in Java.

The implementation of SafeArray's reproduces most of the functionality (and some of the quirks) of the Microsoft® implementation. This means, for example, that you have to size the array in the constructor before you fill it with data otherwise you will get an error, and the fromXXXArray methods do not work for multi-dimensional arrays (which is consistent with the Microsoft® JVM). It would be nice to improve upon this interface, but for now, I put compatibility first.

One extra feature that I couldn't resist adding, is that the com.jacob.com.Variant class is java.io.Serializable. This is accomplished in the C++ code by using the VARIANT_UserMarshal and VARIANT_UserUnmarshal calls from OAIDL.H which are used to serialize a VARIANT to a byte array and back. This feature is reportedly broken on Windows 2000...

Threads, Applets and Security

The original version of JACOB did not handle COM threading issues. This means you had to create and use a COM component in the same thread.

Threading can prove tricky in applets and servlets. For example, the following code makes sure to initialize the COM component in the AWT event thread as opposed to the main applet thread. If you try to create and initialize the ScriptControl in the init() method, the subsequent event code would throw a ComFailException. Please read about the New Threading Model in Version 1.7 to find out about the many options of handling COM threading in JACOB.


import java.awt.event.*;
import java.applet.*;

import com.jacob.com.*;
import com.jacob.activeX.*;

public class AppTest extends Applet implements ActionListener
{
  TextField in;
  TextField out;
  Button calc;
  ActiveXComponent sC = null;
  Object sControl = null;

  public void init() 
  {
    setLayout(new FlowLayout());
    add(in = new TextField("1+1", 16));
    add(out = new TextField("?", 16));
    add(calc = new Button("Calculate"));
    calc.addActionListener(this);
  }

  public void actionPerformed(ActionEvent ev) 
  {
    if (sC == null) 
    {
      // create COM component in event thread
      sC = new ActiveXComponent("ScriptControl");
      sControl = sC.getObject();
      Dispatch.put(sControl, "Language", "VBScript");
    }
    // use COM component in same thread
    Variant v = Dispatch.call(sControl, "Eval", in.getText());
    out.setText(v.toString());
  }
}

In addition to the threading issue, you have to deal with the security restrictions imposed by browsers. If you run this applet in IE5 where the jacob.jar is in your CLASSPATH, then it will work, but if the jacob.jar classes are downloaded - you get a security exception. In JDK1.1.6 appletviewer, this code works as is, but in JDK 1.2.2 appletviewer, loadLibrary fails because there is no security descriptor. So far, I couldn't get the code to run in Netscape 4.6 at all.

COM Object Lifetime

Version 1.7 introduces a new model for COM Object lifetime, please see: COM Object Lifetime in JACOB.

Wrappers

I have included a sample directory called ado. In that directrory are a set of wrappers for ADO which are source-code equivalent to those produced by the Microsoft® JACTIVEX tool. The following excerpt shows how the Connection, Command and Recordset objects interact:

  // create connection and command objects and use them
  // to get a recordset
  public static void getCommand(String con, String query)
  {
     System.out.println("Command+Connection -> Recordset");
     Connection c = new Connection();
     c.setConnectionString(con);
     c.Open();
     Command comm = new Command();
     comm.setActiveConnection(c);
     comm.setCommandType(CommandTypeEnum.adCmdText);
     comm.setCommandText(query);
     Recordset rs = comm.Execute();
     printRS(rs);
     c.Close();
  }

In this release, the wrappers are generated by hand, but please see the related links section above for some 3rd party wrapper generators for JACOB.

Events

I wanted to support events in the first release, but delegating COM events to a proper Java interface was too much work for this timeframe. So, the current implementation departs from the Microsoft® implementation. This will, hopefully, be changed in the future to follow Beans event rules.

The current model is conceptually similar to the Visual Basic WithEvents construct. Basically, I provide a class called com.jacob.com.DispatchEvents which has a constructor that takes a source object (of type com.jacob.com.Dispatch) and a target object (of any type). The source object is queried for its IConnectionPointContainer interface and I attempt to obtain an IConnectionPoint for its default source interface (which I obtain from IProvideClassInfo). At the same time, I also create a mapping of DISPID's for the default source interface to the actual method names. I then use the method names to get jmethodID handles from the target Java object. All event methods currently must have the same signature: one argument which is a Java array of Variants, and a void return type.

The dependency on IProvideClassInfo means that the Type Library for the component must exist if you want to support events. The following example shows how to use the Microsoft® ScriptControl as an in-proc server, and catch the Error and Timeout events which it generates:

Version 1.7 also includes code to read the type library directly from the progid. This makes it possible to work with all the Microsoft Office application events, as well as IE5 events. For an example see the samples/test/IETest.java example.


import com.jacob.com.*;

public class ScriptTest
{
  public static void main(String args[])
  {
    System.runFinalizersOnExit(true);

    String lang = "VBScript";
    Dispatch sControl = new Dispatch("ScriptControl");
    Dispatch.put(sControl, "Language", lang);
    Dispatch.put(sControl, "AllowUI", new Variant(true));
    // instantiate an event target object
    errEvents te = new errEvents();
    // hook it up to the sControl source
    DispatchEvents de = new DispatchEvents(sControl, te);
    // run an expression from the command line
    System.out.println("eval("+args[0]+") = "+ 
                       Dispatch.call(sControl, "Eval", args[0]));
  }
}

// This is the event interface
class errEvents {
  public void Error(Variant[] args)
  {
    System.out.println("java callback for error!");
  }
  public void Timeout(Variant[] args)
  {
    System.out.println("java callback for timeout!");
  }
}


>java ScriptTest 'Now()'
eval(Now()) = 10/5/99 11:07:43 AM

>java ScriptTest '()Now()'
java callback for error!
eval(()Now()) = null

Non-Default Interfaces

Once you have a Dispatch object, you can navigate to the other interfaces of a COM object by calling QueryInterafce. The argument is an IID string in the format: "{9BF24410-B2E0-11D4-A695-00104BFF3241}". You typically get this string from the idl file (it's called uuid in there). Any interface you try to use must be derived from IDispatch. The following example shows how this can be used. The full example (including the ATL COM object) is in the samples\test\atl directory.

import com.jacob.com.*;
import com.jacob.activeX.*;

class MultiFace
{
  public static void main(String[] args)
  {
    System.runFinalizersOnExit(true);

    ActiveXComponent mf = new ActiveXComponent("MultiFace.Face");
    try {
      // I am now dealing with the default interface (IFace1)
      Dispatch.put(mf, "Face1Name", new Variant("Hello Face1"));
      System.out.println(Dispatch.get(mf, "Face1Name"));

      // get to IFace2 through the IID
      Dispatch f2 = mf.QueryInterface("{9BF24410-B2E0-11D4-A695-00104BFF3241}");
      // I am now dealing with IFace2
      Dispatch.put(f2, "Face2Nam", new Variant("Hello Face2"));
      System.out.println(Dispatch.get(f2, "Face2Nam"));

      // get to IFace3 through the IID
      Dispatch f3 = mf.QueryInterface("{9BF24411-B2E0-11D4-A695-00104BFF3241}");
      // I am now dealing with IFace3
      Dispatch.put(f3, "Face3Name", new Variant("Hello Face3"));
      System.out.println(Dispatch.get(f3, "Face3Name"));

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Limitations

Support

The JACOB mailing list WAS hosted at yahoo groups: http://groups.yahoo.com/group/jacob-project. However, due to severe limitations of the yahoo groups software it was getting inordinate amounts of spam and has been DISCONTINUED. Please use the SourceForge link at the top of this page for any support requests.

Contributions

You are encouraged to suggest modifications to the code of the JACOB project. Please refer to the SourceForge link at the top of this page and follow the submission rules of SourceForge.