Chad Verbowski
Software Design Engineer
Microsoft Corporation
January 1999
Introduction
Java Objects in COM
COM Objects in Java
COM Functionality in the Microsoft VM
Implementing a Java/COM Object
Summary
Sources for Additional Information
Sharing data across a network of computers in a bandwidth-efficient and language-neutral manner is essential to developing a cohesive system for doing business. Microsoft has worked hard to achieve a high degree of integration between all of its products. Key to this success has been developing a robust model that provides backward compatibility and language independence. The Component Object Model (COM) was developed to accomplish exactly these tasks and to provide a way to reuse and integrate components at run time. This is a significant development considering that most object-oriented strategies target reusing components at compile time.
Using Java and COM, integration developers can employ Java's advantages and develop components capable of interaction with operating systems, applications, and services written in any COM-compliant language on any platform. Because most of the Microsoft® Windows® functionality is exposed through COM, and most Microsoft applications provide COM interfaces to their object models, Java developers have instant access to rich functionality.
The Microsoft virtual machine (Microsoft VM) provides automatic mapping, allowing a Java object to be a COM object, and allowing any COM object to be accessible as a Java object. This is a powerful feature that makes all of the existing COM-based applications and services available to Java and enables any existing Java objects to be used by other COM supporting languages. The Microsoft VM contains functionality that automatically converts a Java object to a COM component complete with interfaces like IUnknown, IDispatch, IMarshal, and others used for implementing Microsoft® ActiveX® Controls and COM components. The Microsoft VM automatically handles object references, cleanup, and interface queries for any COM object used in Java, whether the component was written in Java or another COM-compliant language. These features, along with garbage collection and the replacement of pointers with references in the language, make Java ideal for many application development projects. Java/COM development offers distinct advantages over C implementations where the developer must contend with memory, threading, and cleanup issues. This is also true for Microsoft® Visual Basic® where developers can quickly implement an application but want more control over how individual components behave.
Modern software projects typically contain an element of basic development where only the features available in the language are used. For example, for Java applications, this means that only the standard Java packages and derivatives from them would be used. By packaging new functionality as COM components, projects will benefit from distinct encapsulation of capabilities and a clear path for future component reuse.
It is quite common to use existing legacy code or third-party APIs to reduce the development time and costs associated with a software project. Integrating these components can be difficult because they could originate from lots of vendors and may be implemented in different languages. Services like Microsoft® Message Queue Server (MSMQ), Microsoft® Transaction Server (MTS), Microsoft® Active Directory (ADSI), Microsoft® Management Console (MMC), and many facilities provided by the Windows operating system are exposed through a set of COM-based objects. Java development projects, through their COM APIs, can easily make use of the Microsoft services in addition to any of the numerous COM components developed by third-party component companies across the industry.
Some software projects benefit by integrating the functionality of other applications. For example, features available through Microsoft® SQL Server, Microsoft® Exchange, Microsoft® Office, Internet Information Server (IIS), Microsoft® Site Server, and Microsoft® Internet Explorer could all be used in your software. All of these COM-based applications and many third-party Windows-based applications expose object models for product integration.
This document explains why Java is an excellent language for COM-component development and why COM is the best method of developing applications to integrate with third-party services and applications.
Exposing a Java object as a COM component requires that the Java object be contained inside a COM-compatible wrapper. The Microsoft VM does this by using a COM-Callable Wrapper (CCW). It neutrally presents the Java object for all other COM components and COM-compliant languages to use. The Microsoft VM dynamically creates a CCW for the Java object when a COM object is required. The CCW is essentially a generic COM object that has its virtual table (vtable) of function pointers dynamically constructed to support some default COM interfaces as well as any COM interfaces implemented by the Java object. The default COM interfaces exposed for every Java/COM object by the CCW allow Java objects to be used as functional COM objects. For example, a Java object registered as a Control will be exposed as an ActiveX component without additional work. The CCW exposed for a Java object is capable of being aggregated, but cannot aggregate another COM object. The Microsoft VM takes care of storing the pointer to the outer COM object's IUnknown interface in the CCW.
The following table lists the default COM interfaces provided by the CCW for Java objects.
Interface | Description |
IUnknown | The Microsoft VM handles the QueryInterface and reference counts for all COM objects (Java implemented or not); there is no need to expose any IUnknown functions to Java developers. |
IDispatch | Provides Automation support. IDispatch assumes that the underlying object is static. |
IDispatchEx | Extends IDispatch; provides Automation support for dynamic languages such as scripting languages. IDispatchEx was developed to support dynamic objects. |
IProvideClassInfo | Provides a single method for accessing the type information for an object's coclass entry in its type library. |
IProvideClassInfo2 | Extends IProvideClassInfo with GetGUID, which returns the object's outgoing IID for its default event set. |
ISupportErrorInfo | Ensures that error information can be correctly propagated up the call chain. Automation objects that use the error handling interfaces must implement this interface. |
IMarshal | Specifies how a COM object can be marshaled between processes and remote computers. |
IConnectionPointContainer | Supports connection points for connection objects. Used to locate a specific connection point. |
IExternalConnection | Manages a served object's count of marshaled (external) connections. The COM object that implements such a count can detect when it has no external connections and correctly shuts itself down. This interface is used by the Microsoft VM for handling the reference counts of COM objects. |
Overriding the functionality of most of these interfaces is done by implementing them. The Java object's COM interface method implementations are added to the vtable instead of the CCW's COM interface method implementations. There are only three interfaces that cannot be overridden: IUnknown, IExternalConnection, and ISupportErrorInfo. This does not pose a problem to Java/COM developers. Because the Microsoft VM deals with these interfaces, the developer is freed from tracking reference counts, querying for interfaces, and propagating exceptions for Automation objects.
When a Java object implements the java.io.Serializable, java.io.Externalizable or com.ms.wfc.core.IComponent interfaces, the CCW implements the following interfaces for that object. COM universally uses these interfaces for serializing COM objects. This allows the Java/COM object to be immediately available as data in an MSMQ message or used in structured storage in conjunction with other COM or ActiveX objects in a document.
Interface | Description |
IPersist | Has only GetClassID method; used to retrieve the CLSID of the object for persisting. |
IPersistStreamInit | This interface is the same as the IPersistStream functions and InitNew; it is used as a replacement for IPersistStream or for persisting objects to a simple stream. |
IPersistStorage | Used to persist an object from a structured storage object. |
A Java class that has been registered as a COM object and specified as a Control (implying the object is an ActiveX Control) will cause the CCW for that Java object to implement the interfaces as shown in the following table.
Interface | Description |
IOleControl | Used for managing keyboard input. |
IOleInPlaceActiveObject | Allows communication with the outer frame. |
IOleInPlaceObject | Manages activation or deactivation of in-place objects. |
IOleObject | Basic functionality for communicating with the container. |
IOleWindow | Used to obtain windows and context-sensitive help. |
IPerPropertyBrowsing | Used for exposing property information in an object browser. |
IViewObject | Used for drawing the component. |
IViewObject2 | Used for drawing the component. |
Any class registered as a Control extending an AWT component that contains superclasses of java.awt.Component and contains the Java interfaces com.ms.ui.IUIComponent and com.ms.object.ISiteable will have the following COM interface implemented in its CCW.
Interface | Description |
IObjectSafety | Exposes functionality for Internet Explorer "Safe for Scripting" and "Safe for Init" security features: 1) Safe for Automation with untrusted scripts and Automation clients. 2) Safe for initialization with untrusted data. 3) Safe for untrusted scripts. |
Any class registered as a Control that contains the com.ms.wfc.core.IComponent interface will have the interface automatically implemented in its CCW.
Interface | Description |
IDataObject | Provides a basic data transfer mechanism, used with the Clipboard and drag-and-drop. |
The component model of the underlying Java object contained in a CCW will affect how the CCW creates the IDispatch(Ex), IProvideClassInfo(2), and IConnectionPointContainer interfaces. Java objects that implement com.ms.wfc.core.IComponent will have an IDispatch interface that contains the methods and properties from the object's ClassInfo class. The IConnectionPointContainer will have entries for each EventInfo contained in the object's ClassInfo. The IProvideClassInfo(2) interfaces provide type library information when it is first needed for a COM component by dynamically creating the type library from the underlying WFC class's ClassInfo. If the underlying Java object doesn't implement the com.ms.wfc.core.IComponent interface, the JavaBeans Introspector is used to gather information about the object's methods, properties, and events for constructing the IDispatch(Ex), IProvideClassInfo(2), and IConnectionPointContainer implementations.
Implementing the Java interface com.ms.com.NoAutoScripting will turn this default behavior off, ensuring that this Java/COM object is not automatically accessible for scripting. When a Java object implements this interface, the Microsoft VM will not add the IDispatch(Ex), IConnectionPointContainer, and IProvideClassInfo(2) interfaces to that object's CCW.
Normally, a Java/COM object's CCW will aggregate the Free-Threaded Marshaler (FTM). It is provided by COM to automatically marshal a COM object to the correct thread when interface function calls are made. This allows the COM object to be used from any thread without requiring the call to be marshaled to the thread hosting the COM object. A Java/COM object can turn this default behavior off by implementing the com.ms.com.NoAutoMarshaling interface, which causes the CCW not to aggregate the FTM. The CCW will effectively be created in the current apartment, which may be either single-threaded (STA) or multi-threaded (MTA).
Ideally, a Java developer would like to use a COM object as if it were a Java object. Accomplishing this requires that each COM object have a Java wrapper for exposing its functionality. The Microsoft VM provides this in the form of a Java-Callable Wrapper (JCW). A JCW is a Java class that has some Microsoft-specific attributes that tell the Microsoft VM how to map the Java object to the COM component that it represents. The jactivex tool provided in the Microsoft SDK for Java can be used to automatically generate JCW Java classes from any dynamic-link library (.dll) file containing COM objects, or from any type library file produced from a compiled COM object's Interface Definition Language (IDL) definition. This tool provides a simple mechanism for generating classes that Java developers can quickly and easily integrate with their project to leverage existing COM facilities.
To create a JCW, compiler directives are added to regular Java source files that specify how the Java object maps to the COM equivalent and vice versa. By providing these mappings in the source file, a Java developer can manipulate them to customize the Java representation of the COM object to suit the particular application's needs. When the Microsoft Java compiler compiles these source files, they are converted into Java class attributes, which the Microsoft VM uses to map the COM object to the Java object. This does not affect the operation of these classes on VMs that don't support COM. These attributes will simply be ignored by VMs that don't understand them.
The following table shows a list of the COM-specific directives that the Microsoft Java compiler uses to create a JCW. For a complete description of the directive parameters available for each directive and how to use them, see the Compiler Directives Reference.
Directives | Description |
@com.class(GUID, security, casting) | Specifies that this Java object represents a COM coclass with the given GUID, casting support, and Java security requirements. |
@com.interface(GUID, thread, type) | Indicates that this Java interface represents a COM interface with the specified GUID, threading model, and types. |
@com.method(dispid, vtable, …) | Marks the Java method as a COM interface method, which should be exposed in the object’s vtable, dispatch table, or neither. |
@com.parameters | Used to specify which parameters are in, out, or a return value and how they should be mapped to COM and from COM to Java. |
@com.typeinfo | Specifies additional information from the type library that represents this COM object. |
@com.transaction | Indicates that the Microsoft Transaction Server can host COM objects in a variety of transaction situations that can be indicated with this directive. |
@com.struct | Specifies that the Java class will be a Java-Callable Data Wrapper (JCDW). |
@com.structmap | Indicates how the field in a JCDW should map to COM and from COM back to this field in Java. This directive can also be used to custom marshal the data between Java and COM and to specify the data's offset from the beginning of the JCDW. |
A new instance of a COM object can be created in Java by simply creating a new instance of a JCW Java class. The Microsoft VM creates the COM object that it refers to in addition to tracking its references. A QueryInterface is performed when a JCW is cast to an implemented COM interface using the Java cast operation. When the Microsoft VM sees this operation, it queries the underlying COM object for the interface represented by the JCW being cast to. The resulting interface is returned from the operation. The Java object can then call any methods on the second COM interface as it would any other Java object. The Microsoft VM caches the queried interfaces so that subsequent cast operations are fast.
The key to integrating Java and COM, while maintaining consistency with the Java programming model, is for the Microsoft VM to handle all garbage collection of COM objects, and to map the QueryInterface calls to simple casting operations. This functionality is directly related to how the COM IUnknown interface behaves in Java. The Microsoft VM accomplishes integration by not exposing any of the IUnknown methods to the Java developer and by internally tracking the reference counts. As a result, the developer is freed from possible bugs related to unbalanced reference counts, and avoids the redundant operation of implementing each of the IUnknown methods for each Java/COM object.
The Microsoft VM contains a JCDW classes for the COM VARIANT and SAFEARRAY data types. Variants are objects that hold primitive data types, IUnknown, or IDispatch references. Variants can be used as parameters where multiple data types may be passed as parameters, and the actual data type will be determined at run time. Variants are commonly used in Visual Basic and in COM components for passing data of variable type through Automation interfaces.
A SafeArray object is a COM data type that allows Visual Basic to interact with COM components to pass around variable sized arrays. SafeArrays contain Variant-compatible types and information that describes the type of Variant contained, the starting position of the first element, and the overall length of the SafeArray. The Variant and SafeArray JCDWs contain Java methods that wrap the COM functions used to access the native data types. This makes using Java Variants and SafeArrays intuitive for developers and allows for painless interaction with Visual Basic through COM.
The Microsoft VM handles the mapping of Java objects to CCWs and from COM objects to JCWs. As such, this section provides details about these two issues. It is interesting to see how the Microsoft VM handles creating an instance of a COM object, and how it performs a QueryInterface call to obtain the currently required interface.
The create instance process described here covers a Java client creating a new instance of a Java-implemented in-process COM object, which is the superset of possible create instance scenarios. This section notes which information is applicable to non-Java clients creating an instance of a Java-implemented COM object, and for a Java client creating an instance of a non-Java/COM object.
When the Java new operator is applied to a JCW Java object1, the Microsoft VM analyzes the Java object’s class file for the COM_Class_Type attribute generated by the @com.class directive. The Microsoft VM uses the information from this attribute to determine the CLSID of the underlying COM object. It also looks up the necessary information for creating an instance of the COM object from the registry. If the COM object to create is a Java-implemented COM object, the InprocServer32 registry key has the value of the Microsoft VM, Msjava.dll (the default in-process server for Java/COM objects that are registered using the Javareg.exe tool from the Microsoft SDK for Java).
1 All COM objects are accessed from Java through JCWs, even if they are implemented in Java.
The Msjava.dll implementation of DllGetClassObject initially goes to the local registry and again looks under
HKEY_CLASSES_ROOT\CLSID\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\ for subkeys as shown in the following table.
Subkey | Description |
JavaClass | The value of this key will be a fully qualified class name that represents the Java object to be exposed as a COM object. |
CodeBase | This will be appended to the class path when the Java object specified by the JavaClass key is created. If this key is not present, only the Java system classes and those located in the \Trustlib directory will be available in the class path. |
Control | Marks the Java/COM object as being an ActiveX control. The CCW provided for the Java object will implement additional interfaces required by ActiveX controls. |
The Microsoft VM provides a generic class factory for Java/COM objects and initializes it with the information from the registry subkeys in the previous table. Inside the Microsoft VM provided IClassFactory::CreateInstance method, a CCW is created for the Java object. The CCW then creates a COM vtable for each Java-implemented COM interface. Each interface from the CCW is automatically supplied for the COM object. If the Java/COM object created is being aggregated, a pointer to the outer COM objects IUnknown interface is stored in the CCW. The Microsoft VM uses CoCreateInstance to create COM objects that were not implemented in Java.
The actual instance of the Java object contained within the CCW has not yet been created. The actual Java object underlying the returned COM object is created the first time an instance of it is required to answer a COM function call. So, for IUnknown::QueryInterface to work, no Java object instance is required. For an IDispatch method invocation to work, however, a Java object instance would be created and maintained. The first method that requires a Java object creates the instance.
Whenever a COM object is passed from the Microsoft VM to Java, the Microsoft VM analyzes the Java type that the Java application expects. If the return type is java.lang.Object and the COM object is actually a CCW, the Java object contained in the CCW will be returned. Similarly, if the return type is a COM interface JCW (implemented by the CCW's contained Java object), the underlying Java object will be returned. Otherwise, the COM object will be contained within an instance of the specified JCW return type. The Microsoft VM provides a generic JCW (com.ms.com.CUnknown) that can represent any COM object when a JCW is not specified as the return type. A generic JCW can be created for any Java object at run time by using com.ms.com.ComLib.makeProxyRef(Object o). The resulting com.ms.com.CUnknown JCW object will refer to a CCW that wraps the specified Java object.
The Microsoft VM performs QueryInterface calls on COM objects. This happens whenever a JCW representing a COM object or interface is cast to a JCW representing a COM interface. This implementation allows COM objects to naturally fit into Java development. Effectively, any COM object can be queried for a COM interface in the same way that a Java object can be queried for a Java interface.
Any Java object (pure or JCW) can be cast to an interface implemented by the object's Java class of the object implements, such as java.io.Serializable. If this cast can't succeed because the class doesn't implement the interface, the Microsoft VM performs some COM-specific operations. The VM ensures that the interface is a COM interface, and the object to be cast is a JCW that supports dynamic casts (an option specified in the JCW's @com.class directive). If these conditions are met, the Microsoft VM queries the underlying COM object for the requested COM interface.
Once the Microsoft VM has verified that the Java application is attempting to perform a valid COM QueryInterface operation, it checks the underlying COM object for a previous query for the interface that is being casted to. It does this by looking in the JCW for the COM object's cache of interfaces already returned to the Java client. If the COM interface is in the cache, it is simply retrieved and returned. This table of interfaces is essentially a hash table keyed by IID.
If the required interface is not already in the cache, the Microsoft VM retrieves the underlying COM object from the JCW. QueryInterface is then called on the CCW/COM object for the requested interface. If an interface pointer is returned, it is stored in the JCW's cache and returned from the casting operation. If the QueryInterface operation on the underlying COM object fails, an error is returned to the Java application.
Visual Basic programmers have had easy application development with COM components through inclusion of the Automation interfaces in the COM specification. Automation allows scripting languages (such as VBScript and JavaScript) to interact with COM objects through the IDispatch interface and late binding with the COM object functions. Automation in COM is similar to manipulating Java objects through the reflection APIs. Essentially, any method can be called on a COM object by specifying its method name and parameters at run time. Further, this has the disadvantage of being slower than vtable binding, as COM must obtain a pointer to the method at run time, and there is no compile time assurance that the correct method name and parameters are used. Automation-compatible interfaces and methods are allowed to use only Automation-compatible types as parameters and return values. A limited number of Automation-compatible types exist, and there is no support for structures. The mapping of Java primitives and objects to Automation types is outlined in the Microsoft SDK for Java. See Type Mappings between Java and COM. The Microsoft VM provides automation interfaces for every Java object in its CCW.
Automation interfaces are helpful when developing COM components that will be used by Visual Basic or scripting languages. Vtable interfaces are generally advantageous when developing components that will be used primarily in Java and C. They are somewhat faster to use at run time and use early binding techniques (programming errors are likely to be caught at compile time rather than at run time). COM supports implementation of an interface as both a Dispatch interface and a vtable interface. This is known as a dual interface. These interfaces are supported in Java, by means of specifying DUAL in the @com.class directive. For complete information on the @com directives and their use, see the Compiler Directives Reference.
Java is capable of implementing custom interfaces that contain methods for passing standard Automation data types, as well as structures, custom data types, and COM objects. The same is true for COM development in C. The standard method for implementing custom interfaces in a COM object is to define the object and all of its interfaces using IDL, and then compiling it with a MIDL compiler. The Microsoft-provided MIDL compiler is Midl.exe, available in the Platform SDK on the MSDN Library and with Microsoft® Visual C++®. The MIDL compiler takes the .idl file as input and generates a type library (.tlb file) and some C code that implements a proxy and a stub for the custom object. Java classes that represent the custom interfaces and the custom object can be generated using Jactivex.exe provided in the SDK for Java. Jactivex takes any standard type library file and generates corresponding .java files for the COM objects, interfaces, and structures defined in the type library. These Java files contain the correct @com directives for specifying the .java files as JCWs.
A Java object can implement a COM interface in exactly the same way that it implements a Java interface. The object being created does not need @com directives when implementing the interface's methods. At run time, the Microsoft VM uses the information from the implemented COM interface wrapper's @com directives when the CCW is creating its vtable for the Java object. Out-of-process and remote use of a Java/COM object containing custom interfaces requires a proxy/stub DLL to be registered on both the server and client computers. The proxy/stub DLL C source code is generated by Midl.exe when compiling an IDL definition of the COM object. The proxy/stub DLL contains the implementation of the proxy and stub COM objects that COM uses to custom marshal client calls to the real COM object containing the custom interfaces. Without the proxy/stub DLL, remote creation and out-of-process creation of the COM object will fail because the COM infrastructure will not know how to marshal the COM object to the client.
To test a custom interface, register the Java/COM object using Javareg.exe, and register the proxy/stub DLL file using Regsvr32.exe. Once the registration is successful, use Oleview.exe (oleview is packaged with Microsoft® Visual Studio® and the Platform SDK) to create an instance of the Java/COM object. Then, examine the interfaces that oleview displays for the Java/COM object. Along with the standard interfaces exposed for a Java/COM object, the name of the custom interface should also appear. Specify that the Java/COM object creation occurs as a local server. This ensures that the proxy/stub DLL code is working correctly. Oleview is an integral tool for COM development and is used to verify COM objects to ensure that they are operating correctly. This is the first place to look when debugging a COM application.
When specifying a structure or a parameter in a COM interface, it is sometimes necessary to indicate to the Microsoft VM exactly how the parameter or structure should be represented in native code. The @com.structmap directive is used to specify how fields in a Java structure are represented in native code. The @com.parameters directive is used to specify how parameters are represented in native code. These directives can indicate that the Java type should be marshaled by means of user-defined methods when creating the native code representation, and the Java representation from native code. To accomplish this task, the custom marshaler must implement two methods, toExternal and toJava. For example, the JCW representing the IPersistFile interface needs to specify the String parameter in the Load method as custom marshaled. The custom marshaler will be indicated in the parameter as a fully qualified Java class. This is shown in the following example.
@com.parameters([in, type=CUSTOM, customMarshal="com.ms.com.UniStringMarshaller"] pszFileName, [in, type=u4] dwMode)
The Microsoft VM uses com.ms.com.UniStringMarshaller to marshal the string that is passed as the pszFileName parameter. com.ms.com.UniStringMarshaller must implement the toExternal and toJava methods for performing the actual data marshaling.2 The Microsoft VM calls toExternal and passes the memory address that should be filled in with a native memory pointer to where the actual bytes representing the string are located. The Microsoft VM also calls toJava and passes the address in native memory where the bytes representing the string are located. The toJava method is called to construct the Java equivalent string from those bytes. The return value of toJava is assigned to the parameter that was specified to have the custom marshaler.
2 Note that UniStringMarshaller also needs a method called releaseExternal to free the string created by toExternal. This is a requirement because cbByValSize is not defined by the class (the marshaler will allocate a non-fixed size amount of memory).
Creating an instance of a COM object is accomplished through a class factory, which is automatically provided for Java/COM objects by the Microsoft VM. It is sometimes useful, however, to implement your own class factory for a Java/COM object. A class factory contains a CreateInstance method that handles requests for new instances of a particular COM object. The class factory can then decide to return an instance that has already been created if a single instance of the requested object is required. Alternatively, a new instance can be created and returned. The class factory, therefore, controls how an instance of a COM object is created, the thread that the object is created on (important for single-threaded objects), and which instance is returned. Fortunately, the Microsoft VM supports implementing class factories in Java, which is accomplished in the same way that a class factory would be developed in C by implementing IClassFactory or IClassFactory2.
Jexegen.exe (from the Microsoft SDK for Java) can package the Java-implemented class factory as an executable file that registers it under the COM object's CLSID in the LocalServer32 registry key. Then, any time an out-of-process request or remote request for the COM object is received, the Java-implemented class factory will be used.
The essential components for Java/COM integration have been presented, with emphasis on how COM components have been seamlessly integrated with the Microsoft VM. COM development with Java is simpler and less error-prone than implementing similar projects in C. The Microsoft VM handles much of the tedious IUnknown work, and handles creating and querying objects in an easy-to-use way. The Automation features available to Visual Basic developers are also present for Java developers with the option of delving deeper into the specifics of how COM objects are implemented and used. As most Microsoft and third-party products and services export object models for integration through COM interfaces, the Microsoft VM's Java/COM integration provides the ideal environment for developing COM components and applications.
Java/COM Integration Attributes