This section details how COM and Java work together from the programming language perspective. The integration of Java and COM can be achieved by just making changes to the Java VM, and not adding any new keywords or constructs to the Java language. That is, the Java language already has the constructs that allow for the implementation and use of COM objects. In particular, Java, like COM, supports the notion of multiple interfaces on an object.
There are two perspectives from which Java-COM language integration can be viewed. The first is from the perspective of 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 sections below.
The following Java code snippet shows how you 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 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 minor restrictions described below) can be instantiated and called from Java as though it were a "real" Java implemented class.
Note Doing COM programming in Java is completely natural to the Java programmer.
In order to use COM objects and interfaces there needs to be some way to bring the names of those COM objects and interfaces into Java. In COM objects and interfaces are named by very large integers which are guaranteed to be unique--Globally Unique Identifiers--or GUIDs. 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 which describes the programming model of a component(2). The Java programmer will usually not refer to GUIDs (IIDs, CLSIDs, CATIDs) directly. Instead the 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" which inform the Java VM that it is really talking to an external COM component, not a normal Java class.
To understand how this works, let's start with a short overview of how Java automatically handles compilation dependencies. Take the following import statement as an example:
import java.awt.Canvas;
The Java compiler will check along the CLASSPATH for a file called java/awt/Canvas.class. If found, it simply imports the symbols relating to java.awt.Canvas into the name space. 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 same example above, 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 name space 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:
import robocorp.bots.*; // import all of bots type library import robocorp.bots.Robot; // import just the Robot coclass
Development environments will also have an "Auto Import" dialog (much like Visual Basic's "References" dialog) that allows certain type libraries from the registry to be automatically imported for every compilation unit in the project. This dialog simply lists the type libraries in the registry and allows you to bring all components of each type library into your name space simply by checking a check mark next to the type library.
In order to support compilers which don't have the ability to deal with type libraries directly, Microsoft will provide a stand-alone tool which 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 name space is tightly integrated with Java.
In COM the lifetime of an object in memory is controlled by a mechanism known as reference counting. Each object maintains, internally, a count of the references clients have to it. If the reference count ever goes to zero, the component will delete itself from memory. Java, on the other hand, uses garbage collection to handle object lifetime. The Java VM, essentially, keeps track of the reference counts on objects and frees them when they are no longer needed.
The Microsoft Java VM garbage collector unifies these two approaches such that COM reference counting is handled automatically.
To illustrate, the example below, 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 ommitted 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 would be:
import robocorp.bots.*; Robot robbie = new Robot(); robbie.GoForward(10); robbie.Turn(-90);
You'll notice that in the Java example we did not need to release the reference to the Robot object as we did in C++. This is because Java is a garbage collected language and can automatically detect when objects are no longer being used. The end result of this, with respect to COM, is that Java programmers do not have to worry about what is probably the most difficult aspect of COM programming: reference counting.
But what about QueryInterface? COM objects can support multiple interfaces and the examples above only use a single interface (IRobot) on the object?
Java too supports the notion of multiple interfaces on objects. This fact alone is probably the number one reason that Java-COM integration works so well. To take the robot example above further, let's assume that 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 allows for determining the status of the robot. Here's some C++ code where we make use of both interfaces. First we tell the robot to move forward 10 units and then to turn left 90 degrees using the IRobot interface. We then QueryInterface for IRobotDiagnostics and use that interface to tell the robot to display (or speak) a status message:
// Error handling ommitted 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();
It is the assignment statement that does the implicit QueryInterface for IRobotDiagnostics(3).
An explicit cast, like the example above, is necessary just as they are necessary in normal Java interfaces. An exception is thrown if the object does not support the desired interface just as happens with normal Java interfaces. The standard Java instanceof 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 IDL example above returns an HRESULT. Having methods return weird error codes like HRESULTs runs contrary to the philosophy of the Java language (as well as Visual Basic), therefore 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 above implies that it's 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:
void OutputStatusMessage(void);
If we wanted OutputStatusMessage to return a boolean indicating whether the status message was output or not, the correct IDL declaration would be:
HRESULT OutputStatusMessage([out, retval]VARIANT_BOOL* pRetVal);
This would be equivalent to the following Java declaration:
boolean OutputStatusMessage(void);
All this raises the issue of error handling and exceptions. Java has rich support for exception handling and exceptions are used consistently throughout Sun's class libraries. 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 there is only one success code defined (S_OK) but many failure codes. To illustrate here's the C++ example from above with error handling added:
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); }
This 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 Java VM does throw a native Java exception when a call to a COM interface method returns an HRESULT containing a failure code.
With this capability, Java code using COM objects can use Java exceptions naturally. The previous Java example is given below, 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 above 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 known as 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 it's own peculiar style of exception handling for COM objects: 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, VB uses the IErrorInfo protocol to get rich error information from the object; in this example the Error$ string.
Microsoft's Java compiler and VM also use the IErrorInfo protocol to enable rich exception handling for calling COM objects. Here's the 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()); }
Further below, were we discuss 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 our 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 it's internal temperatures 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 is 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()));
In other words the Java programmer can use COM properties but must do so though using method calls to set and get the values of those properties (C/C++ programmers have to do the same).
In the examples above we've illustrated how Java's new operator 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 activation, or binding, 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 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 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 via 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. The same rule holds true for COM interfaces. The implication is that you will never see a COM interface defined via 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 simply 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). For example, assuming the IRobot interface introduced in the previous section were described in IDL like this:
[ object, uuid(6C6971D5-8E69-11cf-A54F-080036F12502)] interface IRobot : IUnknown { ... HRESULT OutputStatusMessage([out, retval]BOOL* pRetVal); ... };
And the IDL file were 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 that 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 runtime the Microsoft Java VM will automatically provide type information for this IDispatch implementation (via 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.
In all of the previous examples the classes defined support COM (e.g. they implement IUnknown and follow all the rules), but they are not necessarily creatable via COM (a "class object" in COM parlance) because no CLSID has been assigned to them. In order to make any piece of code be able to be activated by COM, that 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 thus is stored in a type library. It allows a programmer to assign a CLSID to a particular class; for 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 such a coclass statement, and using the coclass name as the name of a Java class, the Java programmer is able to tell the Java compiler and VM that the Java class in question is a COM class object. The Java 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 ... }
Note that the list of interfaces in the coclass statement does not restrict what interfaces an object actually implements. The example above (MyClass) illustrates this: it implements IUnknown, IDispatch, ISomeInterface, ISomeOther, and IAnother. The code for IUnknown and IDispatch are provided by the Java VM.
Therefore, by simply 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 which 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 super-class 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; } ... }
As you can see from this example, MySubClass inherits all of the behavior of MyCoClass, which is a COM class object. Because of COM's binary standard, MyCoClass could be implemented in programming language, not just Java.
The super member variable of Java's root Object class works for COM super-classes just as it does for Java super-classes as illustrated by the implementation of the Method1override.
In the above example, the aggregate object exposes IUnknown and IDispatch; not any of the other interfaces that may be listed in the super class's coclass. Every COM interface that is to be exposed by the sub-class, even if implemented by the super-class, must be specified via the sub-class's implements modifier (or, if the suclass is a COM class, in its coclass in the type library). This allows a Java class implementor to effectively "filter out" interfaces that the sub-class (the aggregatee) implements which he does not want his super-class (the aggregate) to expose. Java allows the super-class 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 VM the exact set of interfaces the outer object (the sub-class) should support via it's IUnknown::QueryInterface implementation. If the sub-class overrides one or more method of one of these interfaces, that interface will be exposed via delegation. If the sub-class does not override any of the methods of the interface then it will be exposed via aggregation.
All Java classes with at least one public method are exposed by the Java 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: (5)
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?
The answer is the Java VM automatically translates functions like this to functions which take objects as parameters instead. For each of the intrinsic Java types (boolean, byte, char, short, int, float, long, and double) the Java VM provides a Java object equivalent. Thus the VM, treats the Foo method as though it were defined thus:
HRESULT Foo([in] long l, [in] ILong* Out1, [out, retval] long* retval);
Allowing the following Java code to be used:
int A; Integer B; A = Foo(42, B); System.out.println("A = " + A + " and B = " + B.Value());
Notice that the variable B is of type Integer rather than int. Thus it's actually an object.
There are some limitations and restrictions regarding COM-Java integration that should be made clear.
Microsoft is committed to delivering first class Java development tools, as well as the reference implementation of the Java virtual machine for Windows platforms. Microsoft's Component Object Model is the cornerstone of the ActiveX(tm) platform and by providing tight 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 paper discusses how Microsoft is integrating Java and COM.
Note This document is an early release of the final specification. It is meant to specify and accompany software that is still in devel-opment. Some of the information in this documentation may be inaccurate or may not be an accurate representation of the func-tionality of the final specification or software. Microsoft assumes no responsibility for any damages that might occur either directly or indirectly from these inaccuracies. Microsoft may have trademarks, copyrights, patents or pending patent applications, or other intellectual property rights covering subject matter in this document. The furnishing of this document does not give you a license to these trademarks, copyrights, patents, or other intellectual property rights.
(1)The names and final design of these classes is very preliminary and subject to change.
(2)Readers familiar with RPC systems such as DCE/RPC or CORBA can think of a type library as being a portable, binary representation of an interface definition language (IDL) file.
(3)This is very similar to how QueryInterface is supported in Visual Basic 4.0.
(4)Note that the design of the Microsoft COM Java classes (e.g. microsoft.Moniker in this example) is still under development.
(5)Again, remember that Java's int is 64 bits.