| More About Java and COM Integration |
Previous |
Introduction |
Next |
This section details how COM and Java work together from the programming language perspective. Integrate Java and COM by making changes to the Microsoft VM without adding any new keywords or constructs to the Java language. 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.
The following Java code excerpt shows you how to call Java classes in Java:
robocorp.bots.Robot robbie = new project.bots.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:
robocorp.bots.Robot robbie = new robocorp.bots.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.
To use COM objects and interfaces, the names of those COM objects and interfaces need to be brought into Java. In COM, objects and interfaces are named by very large integers that are guaranteed to be unique Globally unique identifiers (GUID). The approach taken involves extending the compiler to directly understand the contents of a type library (which is exactly how COM has been integrated into Visual Basic®). A type library is a essentially a database that describes the programming model of a component(2). Java programmers do not usually refer to GUIDs (interface identifiers, class identifiers, CATIDs) directly. Instead, he will refer to the names within a type library attached to those GUIDs. If the type referenced is a type library, the compiler will convert that type library to one or more .class files. The resulting .class files are tagged with special attributes that inform the Microsoft VM that it is actually communicating with an external COM component, not a Java class.
Importing COM class information is based on how Java automatically handles compilation dependencies. Consider the following import statement as an example:
import java.awt.Canvas;
The Java compiler checkes along the CLASSPATH for a file called java/awt/Canvas.class. If found, it imports the symbols relating to java.awt.Canvas into the namespace. The fully qualified name of Canvas is still java.awt.Canvas, although it can be referred to by its alias Canvas. If the .class file is not found, the compiler searches for a java/awt/Canvas.java somewhere along the class path. If the .java file is found, the source is compiled into java/awt/Canvas.class and the .class file is imported.
Extending this to handle type libraries is done by extending the rules of this auto-compilation feature. Using the previous example, if java/awt/Canvas.java cannot be found, the compiler next searches for java/awt.tlb (.tlb being the standard filename extension of type library files) along the CLASSPATH. If the type library is found, the portion of the type library containing the information about the Canvas class is imported. If the type library is not found, the compiler attempts to build one based on an interface definition language (IDL) file it finds. Once the type information for a class has been successfully imported, the class and its interfaces are integrated into the namespace and the programmer is able to access them just as if they were Java classes and interfaces.
Just as the import statement allows the programmer to import an entire package, an entire type library can be imported as well as only portions of the type library:n
import robocorp.bots.*; // import all of bots type library import robocorp.bots.Robot; // import just the Robot coclass
To support compilers that cannot work with type libraries directly, Microsoft will provide a stand-alone tool that supports building the special .class files from a type library. This will invoke the subset of the compiler which performs the type library to .class file conversion.
Note The COM namespace is tightly integrated with Java.
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.*; Robot 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.
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(3).
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.
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 previous example uses the "catch all" expression to catch all thrown exceptions. Usually programmers want to be able to catch specific exceptions and act upon them. Through a mechanism called the IErrorInfo protocol, COM objects can expose much richer error information than can be conveyed in a single DWORD (the HRESULT). Visual Basic® uses this mechanism to enable its style of exception handling for COM objects, which is the "On Error Goto" statement. For example, in Visual Basic®, you can do the following:
Dim robbie As New Robot
On Error Goto ErrorHandler
robbie.GoForward(10)
robbie.Turn(-90)
goto TheEnd
ErrorHandler:
'Err returns the code for the error
Select Case Err
Case RPC_E_SERVERDIED
MsgBox " The communciations link to the robot failed "
End
'Catch all others
Case Else
MsgBox Error$
End
End Select
TheEnd:
Set robbie = Nothing
If a COM method is called in Visual Basic® and a failure code is detected, it uses the IErrorInfo protocol to get rich error information from the object; in this example the Error$ string.
The Microsoft Java compiler (Jvc) and the Microsoft VM also use the IErrorInfo protocol to enable rich exception handling for calling COM objects. Consider the following Java example:
import robocorp.bots.*;
...
try
{
Robot robbie = new Robot();
robbie.GoForward(10);
robbie.Turn(-90);
}
catch(IRobotException e)
{
System.out.println("Robbie had an error! " + e.GetWhyRobotHadAnError());
}
catch(IExcepInfo e)
{
System.out.println("There was a general error! " + e.GetDescription());
}
The following section covers the implementation of COM objects in Java. You will see how thrown Java exceptions get translated into the appropriate HRESULT/IErrorInfo facilities for non-Java COM clients.
Note Java allows programmers to use exception handling when dealing with COM objects.
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.)
The previous examples have illustrated how the new operator in Java can be used to create a new instance of a COM object. In effect, when the class specified to the new operator is a COM class, new boils down to a CoCreateInstance call. However, COM supports other kinds of activation such as moniker binding. These additional activations (or bindings), mechanisms are enabled through a standard set of classes that wrap the standard COM API. For example, to bind to a moniker in Java, you would use code similar to the following:(4)
import robocorp.bots.*;
import microsoft.Moniker.*;
Moniker moniker = new Moniker("file:\\server\share\file.rob");
Robot robbie = moniker.BindToObject();
robbie.GoForward(50);
...
The previous section discussed the use of COM objects in Java. This section discusses how COM objects can be implemented in Java.
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.
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.
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.
Just as a Java class can singly inherit the implementation of another Java class using the extends modifier, any Java class can "inherit" the implementation of any (aggregatable) COM object. The syntax for specifying this is exactly as though the superclass were a Java class:
import com.microsoft.typelib.MyTypeLib
class MySubClass
extends MyCoClass
{
...
// Method1 is implemented by MyCoClass, we're overriding
// the implementation here
int Method1(void)
{
int n = super.Method1();
if (n < 3)
return n;
return 4;
}
...
}
The previous example shows that MySubClass inherits all of the behavior of MyCoClass, which is a COM class object. Because of the binary standard in COM, MyCoClass could be implemented in programming language, not just Java.
The super member variable of the root Object class in Java works for COM superclasses just as it does for Java superclasses as illustrated by the implementation of the Method1override.
The previous example shows the aggregate object exposes IUnknown and IDispatch rather than the other interfaces that may be listed in the superclass's coclass. Every COM interface that is to be exposed by the subclass, even if implemented by the superclass, must be specified via the subclass's implements modifier (or, if the subclass is a COM class, in its coclass, in the type library). This allows a Java class implementor to effectively "filter out" interfaces that the subclass (the aggregatee) implements that he does not want his superclass (the aggregate) to expose. Java allows the superclass implementation to override zero or more of the methods of any of the inherited interface implementations.
From a COM perspective, this support is implemented via a combination of aggregation and delegation. The implements modifier indicates to the Microsoft VM the exact set of interfaces the outer object (the subclass) should support via its IUnknown::QueryInterface implementation. If the subclass overrides one or more method of one of these interfaces, that interface will be exposed via delegation. If the subclass does not override any of the methods of the interface, it will be exposed via aggregation.
All Java classes with at least one public method are exposed by the Microsoft VM as aggregatable COM objects.
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]);
This section clarifies some limitations and restrictions regarding Java-COM integration.
Microsoft is committed to delivering first-class Java development tools, as well as the reference implementation of the Microsoft VM for Java for Windows platforms. COM is the cornerstone of the ActiveX platform. By providing close integration between Java and COM, Microsoft is enabling customers and developers leverage hundreds of man years of effort put into development tools, training, applications, and other infrastructure while still being able to take advantage of new and exciting technologies such as Java. This article discusses how Microsoft is integrating Java and COM.
| © 1997 Microsoft Corporation. All rights reserved. Terms of Use. |