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
.
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.
The JACOB binary distribution (jacobBin_XX.zip) includes:
com.ms
with com.jacob
(for
example com.ms.com.Variant
maps to
com.jacob.com.Variant
.
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.
Download jacobBin_17.zip Download jacobSrc_17.zip
Download jacobBin_16.zip Download jacobSrc_16.zip
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.*;
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...
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.
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.
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
"{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();
}
}
}