Java/COM
integration
COM
(formerly known as OLE) is the Microsoft Component Object Model, the foundation
of all ActiveX technologies. These include ActiveX Controls, Automation, and
ActiveX Documents. But COM is much more; it’s a specification (and a
partial implementation) for developing component objects that can interoperate
using dedicated features of the operating system. In practice, all of the new
software developed for Win32 systems has some relationship with COM – the
operating system exposes some of its features via COM objects. Third-party
components can be COM, and you can create and register your own COM components.
In one way or another, if you want to write Win32 code, you’ll have to
deal with COM. Here, I’ll just recap the fundamentals of COM programming,
and I’ll assume that you are familiar with the concept of a COM server
(any COM object that can expose services to COM clients) and a COM client (a
COM object that uses the services provided by a COM server). This section kept
things simple; the tools are actually much more powerful, and you can use them
in a more sophisticated way. But this requires a deep knowledge of COM, which
is beyond the scope of this appendix. If you’re interested in this
powerful but platform-dependent feature, you should investigate COM and the
Microsoft documentation on Java/COM integration. For more information, Dale
Rogerson’s “Inside COM” (Microsoft Press, 1997) is an
excellent book.
Since
COM is the architectural heart of all the new Win32 applications, being able to
use, or to expose, COM services from Java code can be important. The Java/COM
integration is no doubt one of the most interesting features of the Microsoft
Java compiler and virtual machine. Java and COM are so similar in their models
that the integration is conceptually straightforward and technically seamless
– there’s almost no special code to write in order to access COM.
Most the details are handled by the compiler and/or by the virtual machine. The
effect is that the COM objects are seen as ordinary Java objects by the Java
programmer, and COM clients can use COM servers implemented in Java just like
any other COM server. Again, I use the generic term COM, but by extension this
means that you can implement an ActiveX Automation server in Java, or you can
use an ActiveX Control in your Java programs.
The
most notable similarities between Java and COM revolve around the relationship
between COM interfaces and the Java
interface
keyword.
This is a near-perfect match because:
- A
COM object exposes interfaces (and only interfaces).
- A
COM interface has no implementation; the COM object exposing an interface is
responsible for its implementation.
- A
COM interface is a description of a group of functions semantically related; no
data is exposed.
- A
COM class groups together COM interfaces. A Java class can implement an
arbitrary number of Java interfaces
- COM
has a reference object model; the programmer never “has” an object,
just references to one or more of its interfaces. Java has a reference object
model as well – a reference to an object can be cast to a reference to
one of its interfaces.
- The
lifetime in memory of a COM object is determined by the number of clients using
the object; when this count goes to zero, the object deletes itself from
memory. In Java, the lifetime of an object is also determined by the number of
clients. When there are no more references to that object, the object is a
candidate to be released by the garbage collector.
This
tight mapping between Java and COM not only allows the Java programmer to
easily access COM features, but it also makes Java an interesting language for
writing COM code. COM is language-independent, but the de facto languages for
COM development are C++ and Visual Basic. Compared to Java, C++ is much more
powerful for COM development and generates much more efficient code, but
it’s hard to use. Visual Basic is much easier than Java, but it’s
too far from the underlying operating system, and its object model does not map
very well to COM. Java is an excellent compromise between the two.
Let’s
take a look at some of the keys points of COM development that you need to know
to write Java/COM clients and servers.
COM
Fundamentals
COM
is a binary specification for implementing interoperable objects. For example,
COM describes the binary layout an object should have to be able to call
services in another COM object. Since it’s a description of a binary
layout, COM objects can be implemented in any language that’s able to
produce such a layout. Usually the programmer is freed from these low level
details, since the compiler takes care of generating the correct layout. For
example, if you program in C++, most compilers generate a virtual function
table that is COM-compliant. With languages that do not produce executable
code, such as VB and Java, the runtime takes care of hooking into COM.
The
COM Library also supplies a few basic functions, such as the ones for creating
an object or locating a COM class registered in your system.
The
main goals of a component object model are:
- Let
objects call services into other objects.
- Allow
new types of objects, or upgrades to existing ones, to be seamlessly plugged
into the environment.
The
first point is exactly what object-oriented programming is about: you have a
client object that makes requests to a server object. In this case, the terms
“client” and “server” are used in a generic way, and
not to refer to some particular hardware configuration. With any
object-oriented language, the first goal is easy to achieve if your application
is a monolithic piece of code that implements both the server object code and
the client object code. If you make changes to the way client and the server
objects interface with each other, you simply compile and link again. When you
restart your application, it uses a new version of the components.
The
situation is completely different when your application is made up of component
objects that are not under your control – you don’t control their
source code and they can evolve separately from your application. This is
exactly the case, for example, when you use a third-party ActiveX Control in
your application. The control is installed in your system, and your application
is able, at runtime, to locate the server code, activate the object, link to
it, and use it. Later, you can install a newer version of the control, and your
application should still be able to run; in the worst case, it should
gracefully report an error condition, such as “Control not found,”
without hanging up.
In
these scenarios, your components are implemented in separate executable code
files: DLLs or EXEs. If the server object is implemented in a separate
executable code file, you need a standard, operating system supplied method to
activate these objects. Of course, in your code you do not want to use the
physical name and location of the DLL or EXE, because these might change; you
want some identifier maintained by the operating system. Also, your application
needs a description of the services exposed by the server. This is exactly what
I’ll cover in the next two sections.
GUIDs
and the Registry
COM
uses structured integer numbers, 128 bits long, to unequivocally identify COM
entities registered in the system. These numbers, called GUIDs
(Globally Unique IDentifiers) can be generated by specific utilities, and are
guaranteed to be unique “in space and in time,” to quote Kraig
Brockschmidt. In space, because the number is generator reads the id of your
network card, and in time because the system date and time are used as well. A
GUID can be used to identify a COM class (in which case it’s called a
CLSID) or a COM interface (IID). The names are different but the concept and
the binary structure are the same. GUIDs are also used in other situations that
I will not cover here.
GUIDs,
along with their associated information, are stored in the Windows
Registry, or Registration Database. It’s a hierarchical database, built
into the operating system, which holds a great amount of information about the
hardware and software configuration of your system. For COM, the Registry keeps
track of the components installed in your system, such as their CLSIDs, the
name and location of the executable file that implement them, and a lot of
other details. One of these details is the ProgID of the component; a ProgID is
conceptually similar to a GUID in the sense that it identifies a COM component.
The difference is that a GUID is a binary, algorithmically-generated value,
whereas a ProgID
is a programmer-defined string value. A ProgID is associated with a CLSID. A
COM component is said to be registered in the system when at least its CLSID
and its executable file location are present in the Registry (the ProgID is
usually present as well). Registering and using COM components is exactly what
we’ll do in the following examples.
One
of the effects of the Registry is as a decoupling layer between the client and
server objects. The client activates the server using some information that is
stored in the Registry; one piece of information is the physical location of
the server executables. If the location changes, the information in the
Registry is updated accordingly, but this is transparent to the client, which
just uses ProgIDs or CLSIDs. In other words, the Registry allows for location
transparency of the server code. With the introduction of DCOM (Distributed
COM), a server that was running on a local machine can even be moved to a
remote machine on the network, without the client even noticing it (well,
almost...).
Type
Libraries
Because
of COM’s dynamic linking and the independent evolution of client and
server code, the client always needs to dynamically detect the services that
are exposed by the server. These services are described in a binary,
language-independent way (as interfaces and method signatures) in the type
library
.
This can be a separate file (usually with the .TLB extension), or a Win32
resource linked into the executable. At runtime, the client uses the
information in the type library to call functions in the server.
You
can generate a type library by writing a Microsoft Interface Definition
Language (MIDL) source file and compiling it with the MIDL compiler to generate
a .TLB file. MIDL is a language that describes COM classes, interfaces, and
methods. It resembles the OMG/CORBA IDL in name, syntax, and purpose. The Java
programmer has no need to use MIDL, though. A different Microsoft tool,
described later, reads a Java class file and generates a type library.
Function
return codes in COM: HRESULT
COM
functions exposed by a server return a value of the predefined type HRESULT. An HRESULT
is an integer containing three fields. This allows for multiple failure and
success codes, along with additional information. Because a COM function
returns an HRESULT, you cannot use the return value to hand back ordinary data
from the function call. If you must return data, you pass a pointer to a memory
area that the function will fill. This is known as an
out
parameter
.
You don’t need to worry about this as a Java/COM programmer since the
virtual machine takes care of it for you. This is described in the following
sections.
MS
Java/COM Integration
The
Microsoft Java compiler, Virtual Machine, and tools make life a lot easier for
the Java/COM programmer than it is for the C++/COM programmer. The compiler has
special directives and packages for treating Java classes as COM classes, but
in most cases, you’ll just rely on the Microsoft JVM support for COM, and
on a couple of external tools.
The
Microsoft Java Virtual Machine acts as a bridge between COM and Java objects.
If you create a Java object as a COM server, your object will still be running
inside the JVM. The Microsoft JVM is implemented as a DLL, which exposes COM
interfaces to the operating system. Internally, the JVM maps function calls to
these COM interfaces to method calls in your Java objects. Of course, the JVM
must know which Java class file corresponds to the server executable; it can
discover this information because you previously registered the class file in
the Windows Registry using
Javareg,
a utility in the Microsoft Java SDK.
Javareg
reads a Java class file, generates a corresponding type library and a GUID, and
registers the class in the system. Javareg
can be used to register remote servers as well, for example, servers that run
on a different physical machine.
If
you want to write a Java/COM client, you must go through a different process. A
Java/COM client is Java code that wants to activate and use one of the COM
servers registered on your system. Again, the virtual machine interfaces with
the COM server and exposes its services as methods in a Java class. Another
Microsoft tool, jactivex,
reads a type library and generates Java source files that contain special
compiler directives. The generated source files are part of a package named
after the type library you specified. The next step is to import that package
in your COM client Java source files.
Let’s
look at a couple of examples.
Developing
COM servers in Java
This
section shows the process you will apply to the development of ActiveX
Controls, Automation Servers, or any other COM-compliant server. The following
example implements a simple Automation server that adds integer numbers. You
set the value of the
addend
with the
setAddend( )
method, and each time you call the
sum( )
method the
addend
is added to the current
result.
You retrieve the
result
with
getResult( )
and reset the values with
clear( ).
The Java class that implements this behavior is straightforward:
public class Adder {
private int addend;
private int result;
public void setAddend(int a) { addend = a; }
public int getAddend() { return addend; }
public int getResult() { return result; }
public void sum() { result += addend; }
public void clear() {
result = 0;
addend = 0;
}
}
To
use this Java class as a COM object, the
Javareg
tool is applied to the compiled
Adder.class
file. This tool has a number of options; in this case we specify the Java class
file name (“Adder”), the ProgID we want to put in the Registry for
this server (“JavaAdder.Adder.1”), and the name we want for the
type library that will be generated (”JavaAdder.tlb”). Since no
CLSID is given,
Javareg
will generate one; if we call
Javareg
again on the same server, the existing CLSID will be used.
javareg /register
/class:Adder /progid:JavaAdder.Adder.1
/typelib:JavaAdder.tlb
Javareg
also registers the new server in the Windows Registry. At this point, you must
remember to copy your
Adder.class
file into the Windows\Java\trustlib directory. For security reasons, related
mostly to the use of COM services by applets, your COM server will be activated
only if it is installed in the trustlib directory.
You
now have a new Automation server installed on your system. To test it, you need
an Automation controller, and “the” Automation Controller is Visual
Basic (VB). Below, you can see a few lines of VB code. On the VB form, I put a
text box to input the value of the addend, a label to show the result, and two
push buttons to invoke the
sum( )
and
clear( )
methods. At the beginning, an object variable named
Adder
is declared. In the
Form_Load
subroutine, executed when the form is first displayed, a new instance of the
Adder
automation server is instantiated and the text fields on the form are
initialized. When the user presses the “Sum” or “Clear”
buttons, appropriate methods in the server are invoked.
Dim Adder As Object
Private Sub Form_Load()
Set Adder = CreateObject("JavaAdder.Adder.1")
Addend.Text = Adder.getAddend
Result.Caption = Adder.getResult
End Sub
Private Sub SumBtn_Click()
Adder.setAddend (Addend.Text)
Adder.Sum
Result.Caption = Adder.getResult
End Sub
Private Sub ClearBtn_Click()
Adder.Clear
Addend.Text = Adder.getAddend
Result.Caption = Adder.getResult
End Sub
Note
that this code has no knowledge that the server was implemented in Java.
When
you run this program and the
CreateObject( )
function is called, the Windows Registry is searched for the specified ProgID.
Among the information related to the ProgID is the name of the Java class file,
so in response the Java Virtual Machine is started, and the Java object
instantiated inside the JVM. From then on, the JVM takes care of the
interaction between the client and server code.
Developing
COM clients in Java
Now
let’s jump to the other side and develop a COM client in Java. This
program will call services in a COM server that’s installed on your
system. The example is a client for the server we implemented in the previous
example. While the code will look familiar to a Java programmer, what happens
behind the scenes is quite unusual. This example uses a server that happens to
be written in Java but applies to any ActiveX Control, ActiveX Automation
server, or ActiveX component installed in your system for which you have a type
library.
First,
the
Jactivex
tool is applied to the server’s type library.
Jactivex
has a number of options and switches, but in its basic form it reads a type
library and generates Java source files, which it stores in your
windows/Java/trustlib
directory. In the example line below, it is applied to the type library that
was generated for out COM Automation server.
jactivex
/javatlb JavaAdder.tlb
If,
after
Jactivex
has finished, you take a look at your
windows/Java/trustlib
directory, you’ll find a new subdirectory called
javaadder
that contains the source files for a new package. This is the Java equivalent
of the type library. These files use compiler directives specific to the
Microsoft compiler: the
@com
directives. The reason
jactivex
generated more than one file is that COM uses more than one entity to describe
a COM server (and also because I did not fine-tune the use of MIDL files and
the Java/COM tools).
The
file named
Adder.java
is the equivalent of a
coclass
directive in a MIDL file: it’s the declaration of a COM class. The other
files are the Java equivalent of the COM interfaces exposed by the server.
These interfaces, such as
Adder_DispatchDefault.java,
are dispatch interfaces, part of the mechanism of interaction between an
Automation controller and an Automation server. The Java/COM integration
feature also supports the implementation and use of dual interfaces. IDispatch
and dual interfaces are beyond the scope of this appendix.
Below,
you can see the client code. The first line just imports the package generated
by
jactivex.
Then an instance of the COM Automation server is created and used, as if it was
an ordinary Java class. Notice the typecast on the line where the COM object is
instantiated. This is consistent with the COM object model. In COM, the
programmer never has a reference to the whole object; instead, the programmer
can only have references to one or more of the interfaces implemented in the
class.
Instantiating
a Java object of the Adder class tells COM to activate the server and to create
an instance of this COM object. But then we must specify which interface we
want to use, choosing among the ones implemented by the server. This is exactly
what the typecast does. The interface used here is the
default
dispatch interface
,
the standard interface that an Automation controller uses to communicate with
an Automation server (for details, see
Inside
COM
,
ibid.). Notice how simple it is to activate the server and select a COM
interface:
import javaadder.*;
public class JavaClient {
public static void main(String [] args) {
Adder_DispatchDefault iAdder =
(Adder_DispatchDefault) new Adder();
iAdder.setAddend(3);
iAdder.sum();
iAdder.sum();
iAdder.sum();
System.out.println(iAdder.getResult());
}
}
Now
you can compile and run the code.
The
com.ms.com package
The
com.ms.com
package defines a number of classes for COM development. It supports the use of
GUIDs – the
Variant
and
SafeArray
Automation types – interfacing with ActiveX Controls at a deeper level
and handling COM exceptions.
I
cannot cover all of these topics here, but I want to point out something about
COM exceptions. By convention, virtually all COM functions return an HRESULT
value that tells you if the function invocation succeeded or not and why. But
if you look at the Java method signature in our server and client code, there
no HRESULT. Instead, we use the function return value to get data back from
some functions. The virtual machine is translating Java-style function calls
into COM-style function calls, even for the return parameter. But what happens
inside the virtual machine if one of the functions you call in the server fails
at the COM level? In this case, the JVM sees that the HRESULT value indicates a
failure and generates a native Java exception of class
com.ms.com.ComFailException.
In this way, you can handle COM errors using Java exception handling instead of
checking function return values.
To
learn more about the classes in this package, please refer to the Microsoft
documentation.
ActiveX/Beans
integration
An
interesting result of Java/COM integration is the ActiveX/Beans
integration, by which a Java Bean can be hosted by an ActiveX container such as
VB or any Microsoft Office product, and an ActiveX Control can be hosted by a
Beans container such as Sun’s BeanBox. The Microsoft JVM takes care of
the details. An ActiveX Control is just a COM server exposing predefined,
required interfaces. A Bean is just a Java class that is compliant with a
specific programming style. At the time this was written, however, the
integration was not perfect. For example, the virtual machine is not able to
map the JavaBeans event model to the COM event model. If you want to handle
events from a Bean
inside an ActiveX container, the Bean must intercept system events such as
mouse actions via low-level techniques, not the standard JavaBeans delegation
event model.
Apart
from this, the ActiveX/Beans integration is extremely interesting. The concept
and tools are exactly the same as discussed above, so please consult
Microsoft’s documentation for more details.
A
note about native methods and applets
Native
methods face the security issue. When your Java code calls a native method, you
pass control outside of the virtual machine “sandbox.” The native
method has complete access to the operating system. Of course, this is exactly
what you want if you write native methods, but it is not acceptable for
applets, at least not implicitly. You don’t want an applet, downloaded
from a remote Internet server, to be free to play with the file system and
other critical areas of your machine unless you allow it to do so. To prevent
this situation with J/Direct, RNI, and COM integration, only trusted Java code
has permission to make native method calls. Different conditions must be met
depending on the feature the applet is trying to use. For example, an applet
that uses J/Direct must be digitally signed to indicate full trust. At the time
of this writing, not all of these security mechanisms are implemented (in the
Microsoft SDK for Java, beta 2), so keep an eye on the documentation as new
versions become available.