More About Java and COM Integration Previous
Previous
Introduction
Introduction
Index
Index

Java-COM Language Integration Details

Calling COM Objects from Java , Implementing COM Objects in Java , Additional Java-COM Integration Information , Limitations and Restrictions

This section details how COM and Java work together from the programming language perspective. The Java language already has the constructs for the implementation and use of COM objects. In particular, Java, like COM, supports multiple interfaces on an object.

There are two perspectives that Java-COM language integration can be viewed from. The first is using or calling COM objects from Java code. The second is the implementation of COM objects in Java. Java-COM language integration is explored in depth in the following sections.


Calling COM Objects from Java

The following Java code excerpt shows you how to call Java classes in Java:


import robocorp.bots.*;
Irobot robbie = new Robot();
robbie.GoForward(10);
robbie.Turn(-90);

However, if Robot were actually implemented as a COM class (perhaps it was written using Visual Basic® version 4.0), the following code would be used:


import robocorp.bots.*;
Irobot robbie = new Robot();
robbie.GoForward(10);
robbie.Turn(-90);

So what's the difference? There is none. And that's the point. Any COM object (with some of the following minor restrictions) can be instantiated and called from Java as though it was an actual Java implemented class.

Note COM programming in Java is an easy transition for the Java programmer.

Importing COM Class Information

To use COM objects and interfaces, the names of those COM objects and interfaces must be imported into Java using the Jcom command-line tool. The Jcom tool generates .java files for the classes and interfaces described by a type library. These .java files, once compiled into .class files, allow Java programs to use COM services.

A type library is a mechanism defined by COM to store type information; each type library contains complete type information about one or more COM entities, including classes, interfaces, dispinterfaces, and others.

The typical usage of Jcom is as follows:

jcom filename

where filename is the name of the type library (.tlb, .olb, .ocx, .dll or .exe).

For example:

jcom widgets.tlb

This command first creates a \Widgets subdirectory underneath the trusted library directory specified by the registry key HKEY_LOCAL_MACHINE\Software\Microsoft\Java VM\TrustedLibsDirectory. If the trusted library directory is \Windows\Java\Trustlib, the preceding command creates the directory \Windows\Java\Trustlib\Widgets, and fills it with .java files—one for each class, structure, enumeration, and interface described by the widgets.tlb type library.

If the type library contains a importlib statement, Jcom creates a separate Java package in a separate directory for the imported type library.

Reference Counting

Reference counting is a mechanism that controls the lifetime of an object in memory in COM. Each object internally maintains a count of the references that clients have to it. If the reference count ever goes to zero, the component deletes itself from memory. Java, however, uses garbage collection to handle object lifetime. The Microsoft VM, essentially, keeps track of the reference counts on objects and frees them when they are no longer needed.

The Microsoft VM's garbage collector unifies these two approaches: COM reference counting is handled automatically.

The following example, written in C++, shows how a client of an object uses the Release method to decrement the reference count of a COM object:


// Error handling omitted for brevity
IRobot* probbie = NULL;
CoCreateInstance(CLSID_Robot, NULL, CLSCTX_SERVER, IID_IRobot, (void**)&probbie);
probbie->GoForward(10);
probbie->Turn(-90);
probbie->Release();

In Java, the equivalent code is as follows:


import robocorp.bots.*;
IRobot robbie = new Robot();
robbie.GoForward(10);
robbie.Turn(-90);

The previous example in Java did not need to release the reference to the Robot object as did the example in C++. This is because Java is a garbage collected language and can automatically detect when objects are no longer being used. As a result, programming in Java (with respect to COM) does not require contending with what is probably the most difficult aspect of COM programming: reference counting.

QueryInterface

COM objects can support multiple interfaces, but the previous examples use only a single interface (IRobot) on the object. Java also supports multiple interfaces on objects. This is probably the most important reason that Java-COM integration works so well. Consider the previous robot example, but the COM Robot class (CLSID_Robot) implements an interface called IRobotDiagnostics in addition to IRobot. Where IRobot is used to control what the robot does, IRobotDiagnostics determines the status of the robot. The following C++ code makes use of both interfaces. First, the robot is instructed to move forward 10 units, and then turn left 90 degrees using the IRobot interface. The following example then uses QueryInterface for IrobotDiagnostics, and uses that interface to tell the robot to display (or speak) a status message:


//  Error handling omitted for brevity
IRobot* probbie = NULL;
IRobotDiagnostics* probbieDiag = NULL;
CoCreateInstance(CLSID_Robot, NULL, CLSCTX_SERVER, IID_IRobot, (void**)&probbie);
probbie->GoForward(10);
probbie->Turn(-90);
probbie->QueryInterface(IID_IRobotDiagnostics, (void**)&probbieDiag);
probbie->OutputStatusMessage();
probbieDiag->Release();
probbie->Release();

The same code, written in Java, would look like this:


import robocorp.bots.*;
Robot robbie = new Robot();
IRobotDiagnostics robbieDiag = null;
robbie.GoForward(10);
robbie.Turn(-90);
robbieDiag = (IRobotDiagnostics)robbie;
robbieDiag.OutputStatusMessage();

The assignment statement does the implicit QueryInterface for IrobotDiagnostics (this is very similar to how QueryInterface is supported in Visual Basic® version 4.0).

An explicit cast, like the previous example, is necessary just as it is necessary in normal Java interfaces. An exception is thrown if the object does not support the interface you want, just like normal Java interfaces. The standard Java instance of an operator can also be used to determine up-front whether a given interface is supported.

Note Java makes several of the more arcane parts of COM programming simple.

Return Values, Error Handling, and Exceptions

All methods defined in a COM interface must return an HRESULT type. Note that the OutputStatusMessage in the previous IDL example returns an HRESULT. Methods returning unexpected error codes (such as HRESULTs) runs contrary to the philosophy of the Java language (as well as Visual Basic®). As a result, there must be a way to specify which parameter to a method is actually the return value. Fortunately, COM already provides a mechanism for this: the retval keyword in IDL.

The way that OutputStatusMessage is declared in the previous example implies that its return value is of type void. The HRESULT is important only in the case of an error. Therefore, in Java, this method would be declared as follows:


void OutputStatusMessage(void);

If you wanted OutputStatusMessage to return a Boolean value, indicating whether the status message was output, the correct IDL declaration would be as follows:


HRESULT OutputStatusMessage([out, retval]VARIANT_BOOL* pRetVal);

This would be equivalent to the following Java declaration:


boolean OutputStatusMessage(void);

Java has rich support for exception handling. Exceptions are used consistently throughout the class libraries that are provided by Sun Microsystems. But COM doesn't directly support exception handling, so how can it interoperate seamlessly with Java? The key to answering this question is to understand the various ways that COM methods communicate (exceptional) failure information back to their callers.

The primary way is through the HRESULT that every COM interface method must return. The HRESULT is used to return either a success code or a failure code. In almost all cases, only one success code is defined (S_OK), but many failure codes are defined. To illustrate this, the following C++ Robot example is taken from earlier in this article, but with the addition of error handling:


try
{
	IRobot* probbie = NULL;
	HRESULT hr;
	hr = CoCreateInstance(CLSID_Robot, NULL, CLSCTX_SERVER, IID_IRobot, (void**)&probbie);
	if (hr == S_OK)
	{
		hr = probbie->GoForward(10);
		if (hr == S_OK)
			hr = probbie->Turn(-90);
		probbie->Release();
	}

	if (hr != S_OK)
	{
		switch(hr)
		{
			case RPC_E_SERVERDIED:
				ThrowException("The communciations link to the robot failed");
			break;
		...
			case E_UNEXPECTED:
			default:
				ThrowException("Unexpected error!");
			break;
		}
	}
}
catch(CException* e)
{
	ErrorMessage(e.szMsg);
}

The previous code sample illustrates that even though it is not possible to throw exceptions across a COM call, it is possible to easily map the HRESULT return values to exceptions on the calling side. The Microsoft VM does throw a native Java exception when a call to a COM interface method returns an HRESULT containing a failure code.

With this exception handling capability, Java code using COM objects can use Java exceptions naturally. The previous Java example follows, but with error handling added:


import robocorp.bots.*;
...
try
{
	Robot robbie = new Robot();
	robbie.GoForward(10);
	robbie.Turn(-90);
}
catch(Throwable e)
{
	System.out.println("Something bad happened!");
}

The Microsoft Java compiler (Jvc) and the Microsoft VM use the ComException class to wrap an HRESULT. When calling COM methods from Java, you catch ComException objects to handle error conditions. Because ComException is derived from RuntimeException, the compiler does not check whether methods throw ComException objects or not.

When implementing COM classes using Java, ComException objects are thrown to signal error conditions. Because ComException is an abstract class, a ComException object cannot be constructed directly. However, a ComFailException object or a ComSuccessException object can be constructed.

Properties

Java has no notion of object properties as defined by COM. For example, consider the IRobotDiagnostics interface:


[ object, uuid(6C6971D6-8E69-11cf-A54F-080036F12502), dual]
interface IRobotDiagnostics : IDispatch
{
...
	[propset] HRESULT TemperatureSampleFreq([in] long lFrequency);
	[propget] HRESULT TemperatureSampleFreq([out, retval] long* pTemp);
....
};

In Visual Basic®, the notion of properties on objects is directly supported, and thus the following code can be written to tell the robot to sample its internal temperature every 10 seconds:

Dim robbie As New Robot
Dim robbieDiag As IRobotDiagnostics
...
Set robbieDiag = robbie
robbieDiag.TemperatureSampleFreq = 10000
Debug.Print "Sample Frequency set to " + robbieDiag.TemperatureSampleFreq
Set robbieDiag = Nothing
Set robbie = Nothing

Without adding new language features to Java, the following example shows the only way to accomplish the same thing:


import robocorp.bots.*;
Robot robbie = new Robot();
IRobotDiagnostics robbieDiag = null;
...
RobbieDiag = (IRobotDiagnostics)robbie;
robbieDiag.set_TemperatureSampleFreq(10000);
System.out.println("Sample Frequency set to " + Integer(robbieDiag.get_TemperatureSampleFreq()));

You can use COM properties, but you must do so using method calls to set and get the values of those properties. (C/C++ programmers have to do the same.)


Implementing COM Objects in Java

The previous section discussed the use of COM objects in Java. This section discusses how COM objects can be implemented in Java.

When implementing automation objects in Java, you can use automatic IDispatch. The VM implements IDispatch automatically for almost all Java objects. This means that tools such as javatlb or jcom are no longer necessary simply to script a Java object from an automation controller such as Microsoft Visual Basic or Visual Basic Script. To implement a simple automation object in Java:

  1. Implement the Java object and compile it.
  2. Register it using javareg
  3. Ensure that the Java class is visible in the CLASSPATH environment variable.

Reference Counting and QueryInterface

As with using COM objects in Java, the Java language and the Microsoft VM hides all reference counting from the programmer who is implementing COM objects in Java. There is no need to ever implement AddRef() or Release() in Java.

Likewise, because the Microsoft VM provides the implementation of IUnknown, there is no need to implement QueryInterface(); it is handled automatically.

Note Java greatly simplifies the implementation of COM objects.

Implementing COM Interfaces

Normally (in the absence of COM), when defining an implementing an interface on a Java class, the interface being implemented is either declared within the code module (file) where the implementation occurs or in an external .class file, which is imported through the import statement. It is illegal to both define an interface using the interface declaration and import the same interface's declaration from a .class file. This is also true for COM interfaces. The implication is that you will never see a COM interface defined by Java. Instead, a COM interface is defined in IDL, compiled into a type library, and imported into Java as though it were a standard Java .class file.

To implement a given COM interface on a Java class, use the implements modifier on the class declaration with a list of interface names and provide method bodies for each method in the interfaces (excluding QueryInterface, AddRef, and Release). The following example shows the IRobot interface (that was introduced in the previous section) now described in IDL:


[ object, uuid(6C6971D5-8E69-11cf-A54F-080036F12502)]
interface IRobot : IUnknown
{
	...
	HRESULT OutputStatusMessage([out, retval]BOOL* pRetVal);
	...
};

If the IDL file was compiled into the robocorp/bots.tlb type library, the following Java code could be written to implement a robot object:


import robocorp.bots.*;
class RogerRobot implements IRobot
{
	...
	boolean OutputStatusMessage(void)
	{
		return VoiceBox.Speak(CurrentStatus());
	}
	...
}

Every Java class automatically implements IUnknown and IDispatch. The dispinterface implemented by the Idispatch, which Java provides, contains the public methods that are on the default interface for the class. For example, the following Java class implements an Idispatch, which supports the methods MethodA and MethodB:


class Example 
{
	public void MethodA(void)
        { ... }

	public void MethodB(int foo) 
	{ ... }
}

At run time, the Microsoft VM automatically provides type information for this IDispatch implementation (using IDispatch::GetTypeInfo so that clients can avoid the overhead of using IDispatch::GetIDsOfNames to do late binding.

Note Java objects are COM objects. No additional work is required.

Creating a COM Class Object

The classes defined in the previous examples support COM (that is, they implement IUnknown and follow all the rules). These classes cannot necessarily be created through COM (a "class object" in COM terminology) because a CLSID has not been assigned to them. To enable any piece of code to be activated by COM, its object must have a class identifier (CLSID) associated with it and must support a class factory. In Java, this is accomplished by specifying a coclass that describes the COM related attributes of the class. The coclass attribute is part of the IDL language, and is thus stored in a type library. It allows a programmer to assign a CLSID to a particular class. Consider the following example:


// extract from MyStuff.IDL
...
[uuid(2CFB1F60-9150-11cf-B63C-0080C792B782]
coclass MyClass 
{
	interface ISomeInterface;
	interface ISomeOther;
};

By using the import directive to import a type library containing the previous coclass statement and using the coclass name as the name of a Java class, the Java programmer can instruct the Jvc and the Microsoft VM that the Java class in question is a COM class object. The Microsoft VM will automatically provide a class factory for the class. The following code illustrates this:


import project.MyStuff.*;
// The Java class, MyClass has a "coclass" statement in the MyStuff
// typelibrary imported above. The coclass specifies the class's
// CLSID as well as a base set of interfaces implemented by the
// class.
class MyClass implements IAnother
{
	// Method bodies for all of the methods in the
	// two interfaces specified in MyClass's coclass
	// (namely ISomeInterface and ISomeOther)
	...

	// Method bodies for the IAnother interface
	...
}

Be aware that the list of interfaces in the coclass statement does not restrict what interfaces an object actually implements. The previous example (MyClass) illustrates this: It implements IUnknown, IDispatch, ISomeInterface, ISomeOther, and IAnother. The code for IUnknown and IDispatch are provided by the Microsoft VM.

In summary, by putting a coclass statement into a IDL file, compiling that IDL file into a type library using MIDL, importing the resulting type library into a Java compilation unit, and using the coclass name as the name of a Java class, the programmer can create COM class objects. Client code, written in any programming language that supports COM, can then use the objects.


Additional Java-COM Integration Information


By Reference Parameters

COM interface methods can have parameters which pass "simple" types such as integers, floating point values, and characters, by reference. In most cases these parameters are "out" parameters. For example:


HRESULT Foo ([in] long l, [out] long* Out1, [out, retval] long* retval);

In Java, it would seem like you could call this function like this:


int A, B; 
A = Foo(42, &B); 
System.out.println("A = " + A + " and B = " + B);

However, Java does not support parameters which are references to the intrinsic data types, nor does it support pointers. So, how can a COM interface method like Foo be callable from Java?

Reference types are mapped to one-element arrays by the Microsoft VM. Use the following convention to retrieve a reference:


int A;
int B[] = new int[1];
A = Foo(42, B);
System.out.println("A = " + A + " and B = " + B[0]);

int A = new int[1];
int B[] = new int[1];
A = Foo(42, B);
System.out.println("A = " + A[0] + " and B = " + B[0]);


Limitations and Restrictions

This section clarifies some limitations and restrictions regarding Java-COM integration.



Top© 1997 Microsoft Corporation. All rights reserved. Legal Notices.