by Mark Wutka
Microsoft's Java Virtual Machine, found in Java-enabled versions of Internet Explorer, contains several Windows-specific extensions that are not a part of standard Java. One of the most significant extensions is the integration with the Component Object Model, or COM.
CAUTION:
The Java-COM integration in the Microsoft Java Virtual Machine is very platform-specific. You cannot use it to build platform-independent code. Any code that uses the Java-COM integration will certainly not pass the 100% pure Java test. Still, if you work with Microsoft products, and you are able to use Java, you might as well use all the resources at your disposal.
Many people have trouble distinguishing between OLE and ActiveX. In fact, many people say that ActiveX is just a fancy name for OLE. The reason for the confusion is that ActiveX and OLE are both based on COM.
COM provides a way for objects to communicate, whether they are in the same program, different programs on the same machine, or different programs on different machines. The network version of COM, called DCOM (Distributed COM) has only recently come into production with the introduction of NT 4.0. A beta version of DCOM for Windows '95 is also available from Microsoft's Web site at http://www.microsoft.com/oledev. DCOM should be present in future versions of Windows.
The goal of COM is not just to allow objects to communicate, but to encourage developers to create reusable software components. Yes, that is one of the stated goals of object-oriented development, but COM goes beyond a typical object-oriented programming language like Java. In Java or C++, you reuse a component by including it in your program. COM allows you to use components that are present on your machine, but are not specifically a part of your program. In fact, a successful COM object is used by many programs. The key here is that there is one copy of the COM object's code, no matter how many different programs use the object. While you can arrange your Java classes so that there is no more than one copy of a particular class, you have to do it manually. With COM, this reuse is automatic.
Microsoft has embraced the notion of component-based software and has taken great strides to implement applications as a collection of reusable components. Internet Explorer, for instance, is implemented as a collection of components, with just a small startup program. The Java Virtual Machine in Internet Explorer, for instance, is a separate component that can be used by other applications.
Interfaces are the fundamental foundation of COM. If you understand Java interfaces, you understand COM interfaces. In Java, you can define a set of methods for a class, and then also add other definitions by saying that an object implements a certain interface. In other words, in Java, the set of methods implemented by a class is determined by both the class definition and the interfaces it implements. COM doesn't support the notion of classes, only interfaces. The set of methods implemented by a COM object is determined only by the interfaces it implements.
Each interface has a unique identifier, called its GUID (Globally Unique IDentifier), which is a 40-digit number (160 bits) that is generated such that there should never be two identical GUIDs. One of the important aspects of COM is that once you distribute software that presents a particular interface (with its unique GUID), you should never change that interface. You can't add to it, you can't remove things from it, you can't change the method signatures. If someone else is using one of your components and you change the interface in the next release, you'll break their software. If you need to change an interface, just create a new one instead.
NOTE: GUIDs come from the DCE RPC standard where they are known as Universally Unique IDentifiers (UUID).
COM interfaces are defined by the COM Interface Definition Language (IDL), which is a superset of the DCE RPC IDL (an existing standard for remote procedure calls). Don't confuse COM IDL with CORBA IDL. While they perform the same function, their syntax is very different. You can also use OLE's Object Definition Language (ODL) to define COM interfaces. Microsoft offers two different compilers for compiling interfaces. To compilxe IDL files, use MIDL, which comes with the Win32 SDK, or the Platform SDK. To compile ODL files, use MKTYPLIB, which comes with Visual J++, or the ActiveX SDK.
A COM object is accessed one of three basic ways--as an in-proc server, a local server, or a remote server. When you use an in-proc server object, it runs in the same address space as your program. A local server object runs as a separate program on the same machine, while a remote server object runs on a different machine.
Not only do COM interfaces have a unique identifier, so do COM objects. The unique
identifier for an object is called its Class ID (CLSID). A CLSID is really a GUID,
but it serves a specific purpose so it is given a separate name. These CLSID values
are stored in the Windows registry file and are used to find the particular DLL or
EXE file that implements an object. Figure 50.1 shows a registry entry for a CLSID
that happens to be for a PowerPoint application. The various subkeys, such as LocalServer
and InprocHandler, indicate which DLL or EXE file to use when the PowerPoint object
is used as a local server or an in-proc server.
FIG. 50.1
The registry entry for the LocalServer of a CLSID indicates the .EXE file that
provides a specific COM interface.
Other than the actual functions they perform, the only difference between OLE and ActiveX is that they use different interfaces. All OLE and ActiveX interfaces are defined and implemented using COM.
In order to create a COM object, you must first create an interface using either IDL or ODL. Since the MKTYPLIB utility (the ODL compiler) comes with Visual J++, the examples in this chapter use ODL instead of IDL.
Listing 50.1 shows a sample ODL file. There are a number things in this file that may seem foreign. By taking them one at a time, you see that things are not as complicated as they seem.
// JavaObject.odl // First define the uuid for this type library [ uuid(D65E5380-6D58-11d0-8F0B-444553540000) ] // Declare the type library library LJavaObject { // Include the standard set of OLE types importlib("stdole32.tlb"); // Define the uuid for an odl interface that is a dual interface // A dual interface is the most flexible because it supports the // normal interface calling mechanism and also dynamic calling. // [ odl, dual, uuid(D65E5381-6D58-11d0-8F0B-444553540000) ] // Declare the IJavaObject interface (dual interfaces must inherit // from the IDispatch interface) interface IJavaObject : IDispatch { // Declare the reverseString method that takes a string as input // and returns a string HRESULT reverseString( [in] BSTR reverseMe, [out, retval] BSTR *reversed ); // Declare the square method that takes an integer and returns // an integer HRESULT square( [in] int squareMe, [out, retval] int *squared ); } // Declare a class that implements the IJavaObject interface [ uuid(10C24E60-6D5D-11d0-8F0B-444553540000) ] coclass JavaObject { interface IJavaObject; } };
First of all, when you create a set of interfaces with ODL, you compile them into a type library. A type library is to an ODL file what a .CLASS file is to the Java source. A type library must have its own GUID, so the following statement declares the library and its GUID (remember that a GUID is another name for UUID):
[ uuid(D65E5380-6D58-11d0-8F0B-444553540000) ]library LJavaObject
NOTE: It may seem a little awkward, but you define an object's GUID just ahead of the object itself. In other languages, you usually start off with the object itself.
The importlib statement is similar to the import keyword in Java. In this case, it is importing a set of standard OLE definitions. After the importlib comes the definition of the IJavaObject interface:
[ odl, dual, uuid(D65E5381-6D58-11d0-8F0B-444553540000) ] interface IJavaObject : Idispatch {
Notice that the uuid keyword is accompanied by the odl and dual keywords. The bracketed area where you normally define the uuid is used for any kind of attribute. You almost always find uuid there, since every interface and class must have its own unique identifier.
Whenever you define an interface in ODL, you use the odl keyword. The dual keyword specifies that the interface is a dual interface.
COM has two different ways of invoking methods--through a lookup table or through a dispatch interface. The lookup table is better known as a vtable--a virtual method lookup table, similar to the vtable in C++. The dispatch interface allows you to perform dynamic method invocation. When you use a dispatch interface, there is an extra level of lookup that takes place before the method is invoked. This tends to be slower than a vtable method invocation, but is useful to interpreted languages like Visual Basic. In order to allow the maximum flexibility, you can implement your classes with both vtable and dispatch interfaces by declaring them as dual interfaces.
The method definitions also look rather strange:
HRESULT reverseString( [in] BSTR reverseMe, [out, retval] BSTR *reversed );
Believe it or not, the reverseString method really returns a string, and not the HRESULT value you see declared. The HRESULT return value is necessary when creating this dual interface. The actual return value is specified by the [out, retval] attribute for one of the parameters. A parameter with an attribute of [in] is an "input" parameter, while those with an [out] attribute are output parameters.
The BSTR data type is a "basic string" and is the common way to represent strings in COM. There are other ways, but BSTR is compatible with OLE and also Visual Basic. You may be pleasantly surprised to know that the Java-COM compiler translates the definition of reverseString into this rather simple method declaration:
public String reverseString(String reverseMe) throws com.ms.com.ComException
The definition of the JavaObject class at the bottom of the ODL file tells what interfaces a JavaObject class implements. In this case, there is a single interface--IJavaObject. If you look at different classes available on your system, especially OLE servers, you will find that most classes implement several different interfaces.
The MKTYPLIB program that comes with Visual J++, and also the ActiveX SDK, compiles an ODL file into a type library file, which has an extension of .TLB. The MKTYPLIB command to compile JavaObject.odl is:
mktyplib JavaObject.odl
By default, MKTYPLIB uses the C preprocessor, which allows you to use #include and other preprocessor directives. Unfortunately, this only works if you have a C preprocessor. If you don't, you must use the /nocpp option, like this:
mktyplib /nocpp JavaObject.odl
The JavaObject ODL file contains three different GUIDs. You don't have to make
these values up (in fact, you shouldn't). Instead, Visual C++ and the ActiveX SDK
(and probably other packages, too) come with a tool called GUIDGEN which randomly
generates these values. It can format them in a number of ways and can even copy
them to the clipboard automatically so you can paste them into your source code.
Figure 50.2 shows a sample GUIDGEN session.
FIG. 50.2
The GUIDGEN tool automatically generates GUID values for you.
In order to create a Java object that implements one or more COM interfaces, you need to create special wrapper classes using the JAVATLB command. For example, to create the Wrapper classes for the information in JavaObject.tlb (which was compiled from JavaObject.odl), the JAVATLB command would be:
javatlb JavaObject.tlb
For the JavaObject.tlb file, JAVATLB creates an interface called IJavaObject and a Java class called JavaObject. These classes belong to the package javaobject (all lowercase) and are placed in the \WINDOWS\JAVA\TRUSTLIB directory. Remember that packages require their own subdirectory, so if you look in \WINDOWS\JAVA\TRUSTLIB, you will find a directory called javaobject that contains IJavaObject.class and JavaObject.class.
Once the wrappers have been created, you only need to fill in the appropriate methods. Listing 50.2 shows the JavaObjectImpl class that implements the methods in the IJavaObject interface.
import com.ms.com.*; import javaobject.*; public class JavaObjectImpl implements IJavaObject { public String reverseString(String in) throws ComException { StringBuffer buff = new StringBuffer(); // Start at the end of the input string and add characters // to the string buffer. This puts the reverse of the string // into the buffer. for (int i=in.length()-1; i >= 0; i--) { buff.append(in.charAt(i)); } // Return the contents of the buffer as a new string return buff.toString(); } public int square(int val) throws ComException { // Return the square of val return val * val; } }
Once you have compiled JavaObjectImpl (which you must compile with the Microsoft Java compiler--JVC), use the JAVAREG tool to put information about JavaObjectImpl into the system registry. COM uses the registry to locate COM objects and to find out how to run the server for a particular object. The following command registers JavaObjectImpl and gives it a ProgID of "JavaObject":
JAVAREG /register /class:JavaObjectImpl /progid:JavaObject
CAUTION:
Make sure that you do not put .class after JavaObjectImpl in the JAVAREG command. You want to give JAVAREG the name of the class, not the name of the file containing the class.
The ProgID value is a simple name that other programs like Visual Basic can use
to locate the JavaObject class. JAVAREG creates an entry in the HKEY_CLASSES_ROOT
section of the registry called JavaObject, which contains a subkey called CLSID containing
the class ID (GUID) for JavaObjectImpl. Figure 50.3 shows this Registry
entry, as shown by the REGEDIT command.
FIG. 50.3
A ProgID maps a simple text name to a 160-bit CLSID value.
JAVAREG also creates an entry under CLSID in HKEY_CLASSES_ROOT. The entry's key
is the CLSID for JavaObjectImpl (the same CLSID contained in the ProgID
entry for JavaObject). Figure 50.4 shows the entries made under the CLSID as shown
by REGEDIT.
FIG. 50.4
JAVAREG makes a number of entries under the CLSID key.
The final step in making your class available to the rest of the world is to copy the JavaObjectImpl.class file into \WINDOWS\JAVA\TRUSTLIB.
NOTE: If you have installed Windows 95 or Windows NT in a directory other than \WINDOWS, use that directory name followed by \JAVA\TRUSTLIB. For example, if you are running under Windows NT and it is installed in \WINNT, copy your file to \WINNT\JAVA\TRUSTLIB.
If you have run JAVAREG to register your class, and you have copied the class to the TRUSTLIB directory, you should now be able to access your class from other programs. You can create a simple Visual Basic application to access this class. In the declaration section for the VB application, insert the following statement:
Dim javaob as Object
Next, the Form_Load subroutine, which is called when the VB application starts up, should look like this:
Private Sub Form_Load() Set javaob = CreateObject("JavaObject") End Sub
The JavaObject string is the ProgID for the object. If you used something else as the ProgID when you ran JAVAREG, you would use that name here.
Now you can make use of the methods in the JavaObject class. In this example VB application, there are two text fields--Text1 and Text2. The following subroutine takes the text from Text1, runs it through the reverseString method in JavaObject, and puts the resulting text in Text2:
Private Sub Text1_Change() Text2.Text = javaob.reverseString(Text1.Text) End Sub
Figure 50.5 shows this Visual Basic application in action.
FIG. 50.5
A Visual Basic application can use Java objects.
Microsoft Excel and other Microsoft Office products have their own version of Visual Basic built-in. This means, of course, that you can also access Java objects from Excel!
CAUTION
You must use a 32-bit version of Excel in order for this to work. The 16-bit versions do not use 32-bit COM access, and cannot access Java objects. This example is shown with Excel 7.0a from the Office for Windows 95 suite.
To create an Excel function, start Excel and choose Insert, Macro,
Module from the main menu, as shown in Figure 50.6.
FIG. 50.6
To create an Excel function, you need to insert a code module.
The function must access the JavaObject class, and then call reverseString. Listing 50.3 shows the Reverser$ function.
Function Reverser$(reverseMe$) Dim javaob As Object Set javaob = CreateObject("JavaObject") Reverser$ = javaob.reverseString(reverseMe$) End Function
NOTE: Make sure that you have a recent version of the Microsoft Java SDK. The earliest versions had problems with the COM integration.
=Reverser(A1)
Notice that there is no $ at the end of Reverser in this case.
Now, any text you type in A1 will automatically appear reversed in A2. Figure 50.7
shows an example spreadsheet.
FIG. 50.7
Excel can use Java objects to perform interesting functions.
Just as you can access Java objects via COM, Java objects can access other COM-aware objects. This really opens up possibilities for you on the Windows platform. If you want to create a graph, you can use Excel's graphing capabilities. If you want to create a neatly formatted printout, you can create a document in Word and print it. The best part is, you don't have to go through the pain of creating an ODL file, as long as you can get the type library for the application you want to use.
In the case of Microsoft Word, version 7, you can get the type library for free from Microsoft at http://www.microsoft.com/WordDev/TechInfo/wb70en32.tlb.
Once you have a type library, run JAVATLB on the library to create the Java wrappers. For example, the JAVATLB command for the Word 7 type library would be:
javatlb wb70en32.tlb
This command will create a WordBasic interface class that you can use. Since Word runs as a local server object, you have to do a little more work in order to access it.
The LicenseMgr object can access a local server object given the CLSID of the object. You don't want to look up the Word.Basic CLSID yourself and put it in the source code. Instead, you can access the system registry from your program and discover the CLSID at runtime.
The RegKey class gives you access to the registry. Given a RegKey object, you retrieve sub- keys by calling the RegKey constructor with the parent key. For instance, you want the key CLASSES_ROOT\Word.Basic\CLSID. You first need the RegKey for "CLASSES_ROOT." From that, you create a RegKey for "Word.Basic", which is then used to create the RegKey for CLSID.
Once you have the RegKey you want, you access the default value by calling the enumValue method. Listing 50.4 shows a demo program that runs Word 7.0, creates a simple "Hello World" document, and prints it.
import wb70en32.*; import com.ms.com.*; import com.ms.lang.*; public class WordDemo extends Object { public static void main(String[] args) { try { // Get the Registry Key for CLASSES_ROOT RegKey root = RegKey.getRootKey(RegKey.CLASSES_ROOT); // From CLASSES_ROOT, get the key for Word.Basic RegKey wbkey = new RegKey(root, "Word.Basic", RegKey.KEYOPEN_READ); // From Word.Basic, get the CLSID RegKey clsid = new RegKey(wbkey, "CLSID", RegKey.KEYOPEN_READ); // Retrieve the CLSID from the CLSID key (it's the default value) String classID = ((RegKeyEnumValueString)clsid. enumValue(0)).value; // Create a License Manager for accessing local server objects ILicenseMgr lm = (ILicenseMgr) new LicenseMgr(); // Get a reference to WordBasic WordBasic wb = (WordBasic) lm.createInstance(classID, null, ComContext.LOCAL_SERVER); // Create a new file wb.FileNewDefault(); // Insert some text wb.Insert("Hello World!"); wb.InsertPara(); wb.Insert("Hi there!"); // Print the text wb.FilePrintDefault(); } catch (Error e) { e.printStackTrace(); } } }
TIP: Remember to use the jview command to run programs in the MS Java environment, rather than java.
If you want to see the methods available from the WordBasic object, use the OLE
object viewer that comes with Visual J++ (OLE2VIEW) or the ActiveX SDK (OLEVIEW).
Figure 50.8 shows the OLE2VIEW display of one of the methods in the WordBasic object.
FIG. 50.8
OLEview and OLE2view allow you to examine the methods of COM objects.
One of the things you are bound to encounter with the WordBasic object, and others, is that some methods have parameters of the variant type. The Java-COM package comes with a Variant object that allows you to pass variant parameters. For example, if you want to call a method that takes two variant parameters, you create two instances of a Variant object. Variant parameters are used when parameters are optional. For this example, assume that the second parameter is optional and the first one is an integer. The sequence of events would go like this:
Variant p1 = new Variant(); // Create parameter 1 p1.putInt(5); // Make the parameter value = 5 Variant p2 = new Variant(); // Create parameter 2 p2.noParam(); // Don't pass a value for this parametersomeObject.funMethod(v1, v2); // Call the method
Generally, there are put methods for the basic Java types like int, short, double, and so on. Also, for parameters that are passed by reference (ones that can also return a value), you use putXXXRef, like putIntRef, or putDoubleRef. You can also use the getXXX and getXXXRef methods to retrieve the values stored in a Variant object.