Accessing Java objects from COM

J-Integra® incorporates a framework for allowing Java objects to be accessed as though they were COM objects. The Java objects can be sitting on any platform, using any JVM. You could be running a JVM on Solaris -- whatever.

Key characteristics:

In this section:

See also:


Brief Overview

J-Integra® incorporates a pure Java implementation Microsoft's Distributed COM (DCOM) protocol -- all accesses to Java objects from COM objects are made accross a DCOM link.

From a COM perspective, COM objects are instanciated either using a human readable string (for example from VB: GetObject("productionServer:com.mycompany.finance.Stats")) called a moniker, or using a machine readable unique identifer, called a Class ID.

J-Integra® maps monikers and Class IDs to Java classes which run in a specific JVM, identified by a JVM ID. A JVM ID is simply a short name, such as 'productionServer'.

  1. When a JVM starts up, it registers the fact that is running using the following call:

        com.linar.jintegra.Jvm.register("productionServer");
  2. When the JVM is run, you define a Java Property which tells the J-Integra® runtime what TCP/IP port to try to listen on for DCOM requests:

        java -DJINTEGRA_DCOM_PORT=5678 MyMainJavaClass
  3. On each Windows client which needs to access the JVM, you register the name and location of the JVM using J-Integra®'s 'regjvmcmd' tool:

        regjvmcmd productionServer mymachine.mycompany.com[5678]

    The above assumes that the JVM was started on mymachine.mycompany.com.

Having done the above, from VB you could create instances of any public Java class loadable by the JVM which had a default constructor, using Set o = GetObject("productionServer:AnyPublicJavaClass"). You could then invoke all public methods and access all public properties (including static ones) on the new object.



The regjvm and regjvmcmd tools

These tools register the location of a JVM on a Windows client that needs to create instances of Java classes in the JVM. regjvmcmd is the command line version and regjvm is a gui version of the same tool.

regjvm

See here for more detailed documentation on regjvm.

regjvmcmd

Run regjvmcmd without parameters get a summary of the parameters.

In its simplest form, you specify

If you no longer need to have the JVM registered, or if you wish to change its registration, you must first unregister it using regjvmcmd /unregister JvmId


Early (vtbl) Binding: The java2com tool

J-Integra®'s 'java2com' tool analyzes Java classes (using Java's reflection mechanism), and outputs:

Start the 'java2com' tool using the command java com.linar.java2com.Main. Make sure that the J-Integra® runtime (jintegra.jar) is in your CLASSPATH environment variable. You can run this tool on any platform.

The 'java2com' tool displays the following dialog box:

  1. Classes/Packages to Generate

    This panel provides a graphical view of all classes and packages present in the current set of classpaths, which the user may then select for proxy generation by checking the checkbox next to each package or class. In cases where only some of the classes of a package have been selected, this panel will show these classes are grayed checkboxes.

    By default, the core Java libraries and classes in java2com's working directory will be available for selection. The user may add additional JARs or paths to the classpath by clicking the "Add ..." button on the right of the panel, and then browsing for the apropriate classpath element.

    The classes selected in this panel represent the 'root' classes the user wishes to generate. J-Integra® analyzes these classes, and generates COM IDL definitions and Java DCOM marshalling code which can be used to access the Java class from COM. It then performs the same analysis on any classes or interfaces used in parameters or fields in that class, recursively, until all Java classes and interfaces accessible in this manner have been analyzed.

  2. The Output Directory and IDL filename

    The Output Directory field specifies the directory where the IDL file generated by java2com will be placed. This field defaults to the the current directory.

    The IDL Filename field specifies the name of the IDL file generated by java2com. This field defaults to "java2com".

  3. Saving and Loading Settings

    Upon loading, java2com will check the current directory for a file named "java2com.xml". If this file is present, java2com will automatically load its settings from this file. To create such a file, click the File menu and then select the Save Settings ... menu item.

    To load java2com's settings manually from an existing setting files, click the File menu, select the Load Settings ... menu item, and select a valid XML file from which to load java2com's settings.

    To have java2com automatically save the settings upon exiting, click on the Settings menu, and select the Auto-Save Settings on Exit menu item. Note that if the user has not loaded or saved a settings file in the current session, and java2com did not automatically load from java2com.xml upon startup, this menu option will be disabled. Also note that this mode will only save the current settings if the user exits Javacom via the File menu's Exit menu item.

  4. Names...

    Clicking the Settings menu and then selecting the Name Mapping menu item displays Name Mappings dialog box, which allows the user to override java2com's default name mappings for method names and class names. It can also be used to exclude or include specific classes or methods from generation.

    The left panel contains a tree of all the related java packages and the specific classes whose mappings can be modified. Selecting the "Global Mappings" option allows the user to specify name mappings that affect all methods from all classes:

    New global mappings can be added and removed by clicking the Add Global Mapping and Remove Global Mapping buttons. The mapping names can be modified by double clicking the row representing a particular mapping.

    The user may exclude all methods with a specific name by adding a new global mapping, and then unchecking the Generate checkbox. Note that for performance and efficiency reasons, the Global Mappings table already lists a number of methods that are to be excluded.

    To overide the mappings for a specific class, select the Class and package on the tree in the left panel. For example, selecting java.lang.Boolean yields the following dialog:


    Name mappings for this specific class can be modified by double clicking the field in "COM Name" column, and methods can be excluded from generation by unchecking the Generate checkbox. The COM name J-Integra® will use for this class will be shown on the top right COM Name field; the user can change this to a valid Java class name. Note that global mappings that apply to specific classes - as indicated by the globe icon on the right of a particular method - take precedence and can only be modified by modifying the original global mapping.

    For classes that will not be directly used, the user may opt to exclude such classes from generation by unchecking the top left "Generate This Class" checkbox. Note that J-Integra® already exludes some classes from being generated, such as java.lang.Class:


  5. Dump Analysis

    By clicking the Settings menu and then selecting the Dump Analysis menu item, the user may can force java2com to write useful analysis information to the console.

For each public Java interface that 'java2com' discovers, it creates a corresponding COM interface definition. If the Java interface name were: com.linar.finance.Bankable, then the generated COM interface would be named ComLinarFinanceBankable, unless you specify a different name using the 'Names ...' dialog.

For each public Java class that 'java2com' discovers, it creates a corresponding COM interface definition. If the Java class name were: com.linar.finance.Account, then the generated COM interface would be named IComLinarFinanceAccount, unless you specify a different name using the 'Names ...' dialog. In addition if the Java class has a public default constructor, then 'java2com' generates a COM class ComLinarFinanceAccount, unless you specify a different name using the 'Names ...' dialog.

If a Java class can generate Java events, then the generated COM class will have source interfaces (COM events) corresponding to the events supported by the Java class.

You compile the generated IDL file using Microsoft's MIDL tool. This ships with Visual C++, and can be downloaded from the MS web site.

midl prodServ.idl

This will produce a type library called prodServ.tlb, which can be registered as described in the following section.



Early (vtbl) Binding: The regtlb tool

This tool registers a type library on a COM Windows client that wishes to access Java objects using COM's Early Binding mechanism.

Running regtlb we get the following output:

regtlb Intrinsyc Software International, Inc. 
Usage: regtlb typelib jvmid regtlb /unregister typelib typelib is the type library to be registered/unregistered. jvmid is a JVM registered using the 'regjvm' command
  1. In order to register a type library, you specify the name of the type library file to be registered, and the JVM ID of the JVM in which the COM classes described in the type library are to be found: (Please refer to The regjvm tool to learn how you can register a Jvm.)

    If the type library was generated from an IDL file that was in turn generated by the J-Integra® 'java2com' tool, then the 'regtlb' command can automatically determine the Java class name corresponding to each COM class in the type library (the COM class descriptions in the type library are of the form "Java class java.util.Observable (via J-Integra®)").

    If the type library was not generated from a 'java2com' generated IDL file, you will be prompted to give the name of the Java class that is to be instantiated for each COM class:

    This means that when someone attempts to create an instance of Atldll.Class1, J-Integra® will instantiate com.linar.MyJavaClass in the JVM MyJvm. The MyJavaClass class should implement the Java interfaces generated by J-Integra®'s 'com2java' tool from atldll.tlb that are implemented by the COM class Atldll.Class.

  2. In order to unregister a type library, you specify the name of the type library file
    Example:
    The following command unregisters "atldll.tlb". regtlb /unregister atldll.tlb

Important Note: Please do not use this tool to register standard type libraries. For instance, if you use this tool to register Excel type library, this tool will overwrite the registry entries for Excel in your Windows registry and your Excel will not work properly.

Intercepting the instanciation of Java objects

If you wish to control the instanciation of Java objects, create a class which implements the com.linar.jintegra.Instanciator interface. This interface has one method, which looks like this:

  public Object instanciate(String javaClass) throws com.linar.jintegra.AutomationException;

Pass a reference to your instanciator as a second parameter when calling Jvm.register(...):


com.linar.jintegra.Jvm.register("MyJvm", myInstanciator);

The default instanciator used by J-Integra® looks like this:

public final class DefaultInstanciator implements com.linar.jintegra.Instanciator {
public Object instanciate(String javaClass)
throws com.linar.jintegra.AutomationException {
try {
return Class.forName(javaClass).newInstance();
} catch(Exception e) {
e.printStackTrace();
throw new AutomationException(e);
}
}
}

For example this is a VB to Enterprise Java Beans bridge (based on Sun's JNDI Tutorial):

import javax.naming.*;
import java.util.Hashtable;
import com.linar.jintegra.*;

public class VBtoEJB {
public static void main(String[] args) throws Exception {
Jvm.register("ejb", new EjbInstanciator());
Thread.sleep(10000000);
}
}

class EjbInstanciator implements Instanciator {
Context ctx;

EjbInstanciator() throws NamingException {
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://... TBS ...");
ctx = new InitialContext(env);
}


public Object instanciate(String javaClass) throws AutomationException {
try {
try {
return Class.forName(javaClass).newInstance();
} catch(Exception e) {}
return ctx.lookup(javaClass);
} catch (Throwable t) {
t.printStackTrace();
throw new AutomationException(new Exception("Unexpected: " + t));
}
}
}

If you compiled the above, and ran it on a machine (development.company.com) like this:

java -DJINTEGRA_DCOM_PORT=4321 VBtoEJB

Then on a Windows machine you used the J-Integra® 'regjvmcmd' command like this:

regjvmcmd ejb development.company.com[4321]

Then from VB you would then use:

Set myEjb = GetObject("ejb:cn=ObjectName")
MsgBox myEjb.someProperty
myEjb.myMethod "a parameter"



Implementing a singleton Java object

In COM terminology an object is a singleton if there exists only one instance of the object at any time. Each time you call CreateInstance you obtain a reference to the same object. This ensures that all clients access the same instance.

By controlling the instanciation of Java objects you can implement a singleton java object which is accessible from COM clients. Here is an example of what the instanciator would look like for a class called mySingletonClass:

import java.util.*;
import com.linar.jintegra.*;

public class COMtoJava {
public static void main(String[] args) throws Exception {
try {
Jvm.register("MyJvmId", new SingletonInstanciator("MySingletonClass"));

while (true) { // wait forever
Thread.sleep(100000);
}
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}

class SingletonInstanciator implements Instanciator {
String singletonClassname;
static Object singletonObject = null;

SingletonInstanciator(String singletonClassname) {
try {
this.singletonClassname = singletonClassname;
if (singletonObject == null) {
System.out.println("SingletonInstanciator: creating the singleton [" + singletonClassname + "]");
// initialize the singleton
Class classObject = Class.forName(singletonClassname);
singletonObject = classObject.newInstance();
}
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}

public Object instanciate(String javaClass) throws AutomationException {
try {
System.out.println("instanciate for " + javaClass);

// if request is to create the singleton, just return the existing instance.
if (javaClass.equals(singletonClassname)) {
return singletonObject;
} else {
Class classObject = Class.forName(javaClass);
return classObject.newInstance();
}
}
catch (Exception e)
{
System.out.println("Failed to instanciate class " + javaClass);
System.out.println(e.getMessage());
e.printStackTrace();
System.out.println("Throwing exception back to caller.");< BR> throw new AutomationException(e);
}
}
}

And here is a sample MySingletonClass implementation:


public class MySingletonClass {
public MySingletonClass() {
System.out.println("MySingletonClass constructor called.");
}

public int Method1(int val) {
return val + 1;
}
}

If you compiled both of the above, and ran COMtoJava on a machine (development.company.com) like this:

java -DJINTEGRA_DCOM_PORT=4321 COMtoJava

Then on a Windows machine you used the J-Integra® 'regjvmcmd' command like this:

regjvmcmd MyJvmId development.company.com[4321]

Then from VB you would then use:

Set objMySingleton1 = GetObject("MyJvmId:mySingletonClass")
Set objMySingleton2 = GetObject("MyJvmId:mySingletonClass")
MsgBox objMySingleton1 & objMySingleton2

Which would create two references to the same object.