Chapter 21

Building COM Objects with Java

by Bryan Morgan


CONTENTS

Chapter 20, "Calling COM Objects from Java," gives examples showing COM objects being used in code written in the Java programming language. Because of the similarities between COM and Java, the two technologies can work together in a seamless fashion. When Java classes have been built to "wrap" the COM object, these classes can be treated like any other Java class in a Java program.

Chapter 20 gives examples showing Java code calling a COM dynamic link library as well as an ActiveX control in a Web page. This chapter addresses the opposite situation: Just as Java code can be written to call COM objects, it can also be written to implement COM objects. Because COM is a language-independent object model, when a COM object has been implemented in Java and registered on the system, this Java COM object can be used by programmers writing in Visual Basic, C++, Delphi, or other popular Windows programming languages. This chapter examines the process of designing and constructing COM objects as well as the issues involved with their use. The examples demonstrate building a COM object that is used in a Java applet, a Java application, and a Visual Basic application to demonstrate the power of combining Java and COM.

A Brief Review of COM

Several topics discussed in Chapter 16, "Java and the Component Object Model," help lay the groundwork for the material covered here. COM provides the "plumbing" that is used by objects implemented in various programming languages. Using the rules laid down by COM, these objects can coexist and interact in a predictable fashion-even if the objects were developed by different vendors in different years! However, much of the material introduced in Chapter 16 remains unseen by the COM developer programming in Java.

For instance, recall the rule that each COM object must implement, at a minimum, the IUnknown interface. This remains true for COM objects implemented in Java; however, thanks to the methods by which COM and Java were integrated using the Microsoft Virtual Machine, the developer is shielded from the details of implementing this interface (and many other details). Whereas programmers in other languages are forced to use the API functions in COM to initialize COM and retrieve interface pointers, Java programmers can create a COM object by simply using the new() operator. This is identical to the way that standard Java classes are instantiated in Java.

Reference counting is normally accomplished through calls to the AddRef() and Release() methods of the IUnknown interface. However, thanks to Java's garbage-collected memory-management model, the Java runtime environment handles the freeing of any memory associated with an allocated COM object. Finally, containment and aggregation using COM objects is presented in Chapter 16 as a way to support code reuse. The Java programmer can extend an existing COM class through aggregation by using the extends keyword when defining the new class.

To sum up programming COM objects in Java, it is fair to say that the integration between COM and Java is so seamless that another Java programmer could read your completed source code and not even know that COM was being used. In fact, the differences would only show up if someone wanted to use your Java COM object on a platform other than Windows.

Developing COM Objects for Programmers Using Other Languages

Chapter 20 mentions that calling COM/ActiveX objects from Java has its disadvantages. For instance, the charting example given in Chapter 20 will only run within the Microsoft Internet Explorer browser and (currently) on the Windows platform. This can be viewed as a disadvantage in the Internet environment, where many users using many platforms will routinely access your Web-based information or application. However, developing COM objects in Java in no way puts you at such a disadvantage because COM objects are typically developed to accomplish tasks other than providing active content within a Web browser. If you write a powerful, well-designed COM object in Java, your end result will be a standalone object that can be used by programmers coding in Visual Basic, C++, and Delphi. Using distributed COM (DCOM), this object can even be on a remote machine across a network. Therefore, instead of limiting the Java programmer, it opens up the Java language to a whole new world of users. Although Java is still in its infancy, one of its chief criticisms is that is does not "play well" with other languages. Building a Java class as a COM object opens its use up to programmers in many other languages (including Java).

Designing a Simple COM Object

Before worrying about the implementation details involved with constructing a COM or Java object, take a moment to think about the design issues. As mentioned before, the following Java constructs remain the same when constructing COM objects:

Knowing these things, you can set out to design a COM class just as you would design a Java class. The only possible difference is that all methods used to put or get object information must be defined in an interface in the COM class.

NOTE
Although it is possible to design interfaces and then construct classes by implementing those interfaces, most object-oriented programmers simply define all methods as class members by default instead of defining interfaces. Whereas Java allows classes to be used in this way, COM requires it

In the examples used throughout this chapter, a COM class is created to perform string manipulation functions equivalent to the functionality found in the java.lang.String class. (Keep in mind that although Java programmers may take this class for granted, programmers in other languages do not have these built-in capabilities!) When completed, the example demonstrates a generic COM object that programmers can use in any language to perform common string operations.

Examining the java.lang.String class reveals a wide variety of useful functionality that will be implemented in our string object (we'll call it CString). Table 21.1 is a list of the CString class's methods.

Table 21.1. Methods contained in CString.

Method NameDescription
StringLengthDetermines the length of a string
HashCodeComputes the hash code for a string
CharAtPosReturns the character at a specified position within a string
GetCharsReturns a substring based on the specified start and stop positions within a string
EqualsIgnoreCasePerforms string comparison that is not case sensitive
CompareToPerforms a lexicographic comparison of two strings
RegionMatchesCompares substrings within two strings
RegionMatchesIgnoreCasePerforms a comparison of substrings within two strings that is not case sensitive
StartsWithDetermines whether a string starts with a specified prefix
EndsWithDetermines if a string ends with a specified suffix
IndexOfReturns the index of the first specified character within a string
IndexWithinIndexReturns the index of the first specified character within a string within the specified interval
LastIndexOfReturns the last index of the specified character within a string
SubStringReturns a portion of a string from a specified index to the end
SubStringFromToReturns a portion of a string from a specified starting index to an ending index
ConcatConcatenates a string to a specified string
ReplaceReplaces all characters within a string with a specified new character
ToLowerCaseConverts a string to lower case
ToUpperCaseConverts a string to upper case
TrimTrims all leading and trailing white space from a string
BoolToStrReturns the string representation of a boolean value (true or false)
IntToStrReturns the string value of an integer
LongToStrReturns the string value of a long
FloatToStrReturns the string value of a float
DoubleToStrReturns the string value of a double

These methods will be implemented in Java and packaged as a COM object. Listing 21.1 shows the Java method definitions for each of the methods contained in Table 21.1.


Listing 21.1. Java definitions for the methods in Table 21.1.

public int Length(String str);

public int HashCode(String str);

public char CharAtPos(String str, int index);

public void GetChars(String str, int srcBegin, int srcEnd, char dst[],

     int dstBegin);

public boolean EqualsIgnoreCase(String str, String anotherString);

public int CompareTo(String str, String anotherString);

public boolean RegionMatches(String str, int toffset, String other, int offset,

     int len);

public boolean RegionMatchesIgnoreCase(String str, int toffset, String other,

     int ooffset, int len);

public boolean StartsWith(String str, String prefix);

public boolean EndsWith(String str, String suffix);

public int IndexOf(String str, int ch);

public int IndexOfIndex(String theString, String  str, int  fromIndex);

public int LastIndexOf(String str, int ch);

public String Substring(String str, int beginIndex);

public String SubstringFromTo(String str, int beginIndex, int endIndex);

public String Concat(String str1, String str2);

public String Replace(String str, char oldChar, char newChar);

public String ToLowerCase(String str);

public String ToUpperCase(String str);

public String Trim(String str);

public String BoolToStr(boolean b);

public String IntToStr(int i);

public String LongToStr(long l);

public String FloatToStr(float f);

public String DoubleToStr(double d);


Creating the Type Library

As mentioned in this chapter and originally discussed in Chapter 16, COM classes are defined within a type library. This type library is created from an object description language (ODL) object specification file containing the definition of a COM class and its interfaces. This section demonstrates how to create the ODL definition for the CString object using the MKTYPLIB tool included with Visual J++ to build the type library.

To review, the following steps must be completed to build a COM object for use by other applications:

  1. Generate GUIDs for each class and interface to be defined within a type library.
  2. Create an ODL file containing method definitions for all the interfaces within a class.
  3. Compile the ODL file using MKTYPLIB to produce a COM type library.
  4. Generate the Java interface classes to the COM object using JavaTLB (included with Visual J++).
  5. Implement the interfaces in a Java class (or classes).
  6. Register the created Java class (or classes) using JAVAREG (included with Visual J++).

After completing these steps, the COM object is ready to be used by any application capable of calling COM objects. We are now ready to begin the actual construction of the object.

Generating GUIDs for the COM Object

Before generating GUIDs for the COM object, you must first decide how many interfaces and classes will be contained in the defined ODL library. You will be defining one class, CString, that could contain one or more interfaces. For simplicity's sake, group all the methods listed in Table 21.1 into one interface named IString. Although it would be possible to group all the different types of methods into smaller, separate interfaces, nothing would be gained from that exercise from a learning standpoint. (The same effort and knowledge required to create one COM interface is basically duplicated when creating two or more.)

Therefore, the COM object to be built in this chapter consists of a class named CString containing an interface named IString. The contents of the IString interface are the methods listed in Table 21.1. Three GUIDs must be generated to define the elements within the type library: The first will be used to define the library itself; the second will be used to identify the COM class Cstring; and the third will be used to identify the IString interface.

Chapter 16 introduces the GUIDGEN tool, which is capable of generating GUIDs. This tool is included with Visual J++ and can be run from the Tools | Create GUID main menu option. Running this tool three times on my machine produced the following three GUIDs for the type library:


{0AD2C500-18A5-11d0-B4EE-444553540000}

{0AD2C501-18A5-11d0-B4EE-444553540000}

{0AD2C502-18A5-11d0-B4EE-444553540000}

If you run this tool on your machine, you will retrieve a different set of IDs. Therefore, unless you are planning to re-create the entire example, simply use these GUIDs in any code you create throughout the remainder of the chapter.

Building the Shell of a Type Library

Now that the GUIDs have been generated for the CString class, the shell of the COM object can be created. This empty shell is listed in Listing 21.2 and contains no method definitions as of yet.


Listing 21.2. The initial attempt at the CString type library.

// This describes the library Chapter21Lib

[

  uuid (0AD2C500-18A5-11d0-B4EE-444553540000),

  version (1.0),

  helpstring("Visual J++ Unleashed Chapter21 Type Library")

]

library Chapter21Lib

{

  // Chapter21Lib imports the interfaces, classes, structures,

  // types, and so forth from STDOLE32.TLB.

  importlib("stdole32.tlb");



  // This describes the interface IString, a dispinterface.

  // A dispinterface is an IDispatch-type interface.

  // Making this interface derive from IDispatch allows

  // this interface to be used via OLE Automation

  [

    uuid (0AD2C501-18A5-11d0-B4EE-444553540000),

    helpstring("Visual J++ Unleashed IString Interface")

  ]

  dispinterface IString

  {

    properties:

    methods:

  }



  // The coclass that implements the interface

  [

    uuid (0AD2C502-18A5-11d0-B4EE-444553540000),

    helpstring("Visual J++ Unleashed CString Class")

  ]

  coclass CString

  {

    dispinterface IString;

  };

};


What is accomplished in Listing 21.2? Basically, the entire ODL definition for the CString class is created except for the method declarations. The class definition, in fact, is complete. It simply defines dispinterface IString as a member of the CString class.

Two things should be noted about the definition in Listing 21.2. First, note the use of the GUIDs throughout the class definition. As stated earlier, one GUID was generated to be used by the library, the interface, and the class. The second item to note is the dispinterface keyword used to define the IString interface. Declaring an interface in ODL using the dispinterface keyword means that this interface will derive from the IDispatch interface rather than directly from the IUnknown interface. Deriving from the IDispatch interface enables the object to be accessed directly through OLE Automation. In the C programming language, the contents of the IDispatch interface appear as follows:


interface IDispatch : IUnknown

{

  HRESULT GetTypeInfoCount(unsigned int *pctinfo);

  HRESULT GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo **pptinfo);

  HRESULT GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, unsigned int cNames,

     LCID lcid, DISPID *rgdispid);

  HRESULT Invoke(DISPID dispID, REFIID riid, LCID lcid, unsigned short wFlags,

    DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo,

    unsigned int *puArgErr);

};

The Invoke() method is used by OLE Automation controllers (such as Visual Basic) to call a method or access a property within a dispatch interface. The GetIDsOfNames() method converts the physical names of properties or methods to logical dispatch IDs within the dispatch interface. The GetTypeInfoCount() method determines whether type library information is available for the interface. Finally, the GetTypeInfo() method returns type information for the dispatch interface. Fortunately for the Java programmer, the plumbing involved with implementing the IDispatch interface is handled by the Microsoft Virtual Machine. The Java developer must simply implement the additional functions defined in the interface that derives from IDispatch.

Mapping Java Types to ODL Types

Now that you have laid down the outline of the CString class, it is time to start defining the methods in the IString interface. Each method declaration has the following general syntax:


[id(#)]return_type method_name([in/out] data_type param1, 

     [in/out] data_type param2 ...);

Before randomly assigning ID numbers to identify a specific method within dispinterface, note that the id flag is a 32-bit integer number. The first 16 bits (0Ð15) are open for the developer to use. Bits 16Ð31 are reserved to pass additional information to the calling object. (Consult the COM documentation included with Visual J++ to learn more about the id flag.)

The ODL defines its own set of data types, all of which map, in one way or another, to corresponding Java data types (see Table 21.2).

Table 21.2. ODL data types and their Java counterparts.

ODL Data TypeJava Data Type
booleanboolean
charchar
doubledouble
intint
int64long
floatfloat
longint
shortshort
unsigned charbyte
BSTRclass java.lang.String
CURRENCY/CYlong (divide by 10,000 to get the original value as a fixed-point number)
DATEdouble
SCODE/HRESULTint
VARIANTclass com.ms.com.Variant
Iunknown *interface com.ms.com.IUnknown
Idispatch *class java.lang.Object
SAFEARRAY(typename)class com.ms.com.SafeArray
typename *single-element array of typename
voidvoid

For instance, nearly all the methods to be declared in the IString interface use Java String objects in one way or another. To declare a java.lang.String parameter in ODL, the parameter would be declared as type BSTR.

Now that you know the syntax for each method's definition and have an understanding of the available ODL data types, it is time to actually build the ODL definitions for the IString methods. Because these methods were originally defined in Java (refer to Listing 21.1), it is a relatively easy step to convert them to their ODL equivalents. The Example21 library is fully defined in Listing 21.3.


Listing 21.3. The complete definition of the IString interface.

// This describes the library Chapter21Lib

[

  uuid (0AD2C500-18A5-11d0-B4EE-444553540000),

  version (1.0),

  helpstring("Visual J++ Unleashed Chapter21 Type Library")

]

library Chapter21Lib

{

  // Chapter21Lib imports the interfaces, classes, structures,

  // types, and so forth from STDOLE32.TLB.

  importlib("stdole32.tlb");



  // This describes the interface IString, a dispinterface.

  // A dispinterface is an IDispatch-type interface.

  // Making this interface derive from IDispatch allows

  // this interface to be used via OLE Automation

  [

    uuid (0AD2C501-18A5-11d0-B4EE-444553540000),

    helpstring("Visual J++ Unleashed IString Interface")

  ]

  dispinterface IString

  {

    properties:

    methods:

      [id(0)] long Length([in] BSTR str);

      [id(1)] long HashCode([in] BSTR str);

      [id(2)] char CharAtPos([in] BSTR str, [in] long index);

      [id(3)] void GetChars([in] BSTR str, [in] long srcBegin,

        [in] long srcEnd, [in,out] char* dst, [in] long dstBegin);

      [id(4)] boolean EqualsIgnoreCase([in] BSTR str, [in] BSTR anotherString);

      [id(5)] long CompareTo([in] BSTR str, [in] BSTR anotherString);

      [id(6)] boolean RegionMatches([in] BSTR str, [in] long toffset,

        [in] BSTR other, long offset, [in] long len);

      [id(7)] boolean RegionMatchesIgnoreCase([in] BSTR str, [in] long toffset,

        [in] BSTR other, [in] long ooffset, [in] long len);

      [id(8)] boolean StartsWith([in] BSTR str, [in] BSTR prefix);

      [id(9)] boolean EndsWith([in] BSTR str, [in] BSTR suffix);

      [id(10)] long IndexOf([in] BSTR str, [in] long ch);

      [id(11)] long IndexOfIndex([in] BSTR theString, [in] BSTR str,

        [in] long fromIndex);

      [id(12)] long LastIndexOf([in] BSTR str, [in] long ch);

      [id(13)] BSTR Substring([in] BSTR str, [in] long beginIndex);

      [id(14)] BSTR SubstringFromTo([in] BSTR str, [in] long beginIndex,

       [in] long endIndex);

      [id(15)] BSTR Concat([in] BSTR str1, [in] BSTR str2);

      [id(16)] BSTR Replace([in] BSTR str, [in] char oldChar,

        [in] char newChar);

      [id(17)] BSTR ToLowerCase([in] BSTR str);

      [id(18)] BSTR ToUpperCase([in] BSTR str);

      [id(19)] BSTR Trim([in] BSTR str);

      [id(20)] BSTR BoolToStr([in] boolean b);

      [id(21)] BSTR IntToStr([in] long i);

      [id(22)] BSTR LongToStr([in] CURRENCY l);

      [id(23)] BSTR FloatToStr([in] float f);

      [id(24)] BSTR DoubleToStr([in] double d);

  }



  // The coclass that implements the interface

  [

    uuid (0AD2C502-18A5-11d0-B4EE-444553540000),

    helpstring("Visual J++ Unleashed CString Class")

  ]

  coclass CString

  {

    dispinterface IString;

  };

};


The ODL file containing the contents of Listing 21.3 is named Example21.ODL. It can be found in the \TypeLib directory for this chapter on the CD-ROM included with this book.

Using MKTYPLIB to Build the Type Library

When the ODL source code has been created for the IString interface, the type library can be created using the MKTYPLIB tool included with Visual J++. For Java programmers, this tool can be utilized using the following syntax:


mktyplib /nocpp filename.odl

The /nocpp option must be specified on the command line, or else MKTYPLIB will try to use a C preprocessor. In addition to the /nocpp option, MKTYPLIB also accepts several other useful command-line options. For a complete list of these options, see the documentation on MKTYPLIB in Chapter 16.

Before using MKTYPLIB, make sure its directory is in your system PATH. By default, this directory will be C:\MSDEV\BIN. To build the Example21.TLB type library file, simply execute the following command:


mktypelib /nocpp Example21.odl

Running this command creates the EXAMPLE21.TLB file. This file will be utilized by the JAVATLB file to create skeleton Java classes based on the contents of the type library. You may receive warnings about using the char data types in the ODL, because the char data type is not directly supported by the IDispatch::Invoke() method. Therefore, you cannot invoke any methods using this data type through an OLE Automation controller. Because neither the Java nor the Visual Basic examples use these methods, in this example these warnings will be ignored. However, if you were building an application to use these methods, it would be wiser to use the unsigned char data type instead. When this happens, the Java class files that are generated treat the unsigned char data type as a byte value. It would be the programmer's responsibility in the Java implementation to convert this byte value to a character value. For our purposes, however, the example continues as is.

Generating the Java Interface to the COM Object Using JAVATLB

In Chapter 20, the Java Type Library Wizard is used from within the Visual J++ development environment to build Java class files for registered COM objects. The JAVATLB tool is the command-line version of the Java Type Library Wizard that performs a similar task. However, when JAVATLB is used from the command prompt, it can generate class files from a type library even if that type library has not yet been registered. In the previous section, "Using MKTYPLIB to Build the Type Library," you should have built a type library correctly for your CString class. Therefore, at this point you will generate trusted class files from this type library. After making sure that the directory containing JAVATLB.EXE is included in your system PATH statement, run the following command to generate the Java class files:


javatlb Example21.tlb

When this command has been completed successfully, examine the contents of your Java \Trustlib directory. By default, this directory should be c:\windows\java\Trustlib. To determine the exact location of the Trustlib directory on your machine, examine the following registry key:


HKEY_LOCAL_MACHINE\Software\Microsoft\Java VM\TrustedLibsDirectory

A new directory should have been created within your \Trustlib directory named Example21. This directory will contain two Java class files: CString.class and IString.class.

To receive a SUMMARY.TXT file containing the interface method definitions in Java, run JAVATLB with the following syntax:


javatlb /U:T Example21.tlb

This produces the SUMMARY.TXT file shown in Listing 21.4.


Listing 21.4. Example21.TLB summary information created by JAVATLB.

public class example21/CString extends java.lang.Object

{

}

public interface example21/IString extends com.ms.com.IUnknown

{

    public abstract boolean RegionMatches(java.lang.String, int,

      java.lang.String, int, int);

    public abstract boolean RegionMatchesIgnoreCase(java.lang.String, int,

      java.lang.String, int, int);

    public abstract int IndexOf(java.lang.String, int);

    public abstract int IndexOfIndex(java.lang.String, java.lang.String, int);

    public abstract java.lang.String ToLowerCase(java.lang.String);

    public abstract java.lang.String SubstringFromTo(java.lang.String, int,

      int);

    public abstract int LastIndexOf(java.lang.String, int);

    public abstract java.lang.String ToUpperCase(java.lang.String);

    public abstract int Length(java.lang.String);

    public abstract boolean EndsWith(java.lang.String, java.lang.String);

    public abstract boolean StartsWith(java.lang.String, java.lang.String);

    public abstract boolean EqualsIgnoreCase(java.lang.String,

      java.lang.String);

    public abstract java.lang.String Concat(java.lang.String,

      java.lang.String);

    public abstract java.lang.String Trim(java.lang.String);

    public abstract int HashCode(java.lang.String);

    public abstract char CharAtPos(java.lang.String, int);

    public abstract void GetChars(java.lang.String, int, int, char[], int);

    public abstract java.lang.String BoolToStr(boolean);

    public abstract java.lang.String IntToStr(int);

    public abstract java.lang.String LongToStr(long);

    public abstract java.lang.String FloatToStr(float);

    public abstract java.lang.String Replace(java.lang.String, char, char);

    public abstract java.lang.String DoubleToStr(double);

    public abstract int CompareTo(java.lang.String, java.lang.String);

    public abstract java.lang.String Substring(java.lang.String, int);

}


A quick comparison of the summary file contents with the Java methods defined in Listing 21.1 reveals an exact match! Therefore, the type library was created correctly and the JAVATLB tool was able to accurately build Java classes based on the contents of this type library. Now all that is left to do is to actually implement the IString interface in Java and then register the Java class as a COM object. When this is done, sample applications will be built in Java and Visual Basic that make use of this COM object.

Implementing the COM Object in Java

The "new" work is now behind you. You have made use of the JAVATLB and MKTYPLIB tools to build a COM type library and create Java classes based on the contents of that type library. Now you must climb back on familiar ground and implement the IString interface in Java. Because the methods in this interface very closely resemble the member methods of the java.lang.String class, nearly all of these methods will consist of a single line of code.

Listing 21.5 contains the actual implementation of the CString class in Java. This Java class will be named COMString to avoid name conflicts with the CString.class file.


Listing 21.5. Contents of the COMString Java class.

/*

 *

 * COMString

 *

 */



import java.lang.String;



// Import the generated COM interface

import example21.*;



public class COMString implements IString

{

  public int Length(String str)

  {

    return (str.length());

  }



  public int HashCode(String str)

  {

    return (str.hashCode());

  }



  public char CharAtPos(String str, int index)

  {

    return (str.charAt(index));

  }



  public void GetChars(String str, int srcBegin, int srcEnd,

    char dst[], int dstBegin)

  {

    str.getChars(srcBegin, srcEnd, dst, dstBegin);

  }



  public boolean EqualsIgnoreCase(String str, String anotherString)

  {

    return (str.equalsIgnoreCase(anotherString));

  }



  public int CompareTo(String str, String anotherString)

  {

    return (str.compareTo(anotherString));

  }



  public boolean RegionMatches(String str, int toffset, String other, 

       int offset, int len)

  {

    return (str.regionMatches(toffset, other, offset, len));

  }



  public boolean RegionMatchesIgnoreCase(String str, int toffset, 

       String other, int ooffset, int len)

  {

    return (str.regionMatches(true, toffset, other, toffset, len));

  }



  public boolean StartsWith(String str, String prefix)

  {

    return (str.startsWith(prefix));

  }



  public boolean EndsWith(String str, String suffix)

  {

    return (str.endsWith(suffix));

  }



  public int IndexOf(String str, int ch)

  {

    return (str.indexOf(ch));

  }



  public int IndexOfIndex(String theString, String  str, int fromIndex)

  {

    return (str.indexOf(str, fromIndex));

  }



  public int LastIndexOf(String str, int ch)

  {

    return (str.lastIndexOf(ch));

  }



  public String Substring(String str, int beginIndex)

  {

    return (str.substring(beginIndex));

  }



  public String SubstringFromTo(String str, int beginIndex, int endIndex)

  {

    return (str.substring(beginIndex, endIndex));

  }



  public String Concat(String str1, String str2)

  {

    return (str1.concat(str2));

  }



  public String Replace(String str, char oldChar, char newChar)

  {

    return (str.replace(oldChar, newChar));

  }



  public String ToLowerCase(String str)

  {

    return (str.toLowerCase());

  }



  public String ToUpperCase(String str)

  {

    return (str.toUpperCase());

  }

  public String Trim(String str)

  {

    return (str.trim());

  }



  public String BoolToStr(boolean b)

  {

    return (String.valueOf(b));

  }



  public String IntToStr(int i)

  {

    return (String.valueOf(i));

  }



  public String LongToStr(long l)

  {

    return (String.valueOf(l));

  }



  public String FloatToStr(float f)

  {

    return (String.valueOf(f));

  }



  public String DoubleToStr(double d)

  {

    return (String.valueOf(d));

  }

}


Each method in this class simply calls the corresponding method within the java.lang.String class. This class implements the IString interface and therefore implements all the IString methods. If any of these methods were left off, the compiler would generate an error.

Registering the Java Classes Using JAVAREG

One last step remains before the COMString class can be used as a COM object by any other application: registering the new COM object in the Windows Registry. Visual J++ comes with a command-line tool named JAVAREG that will insert the proper entries into the Registry based on the options passed to it by the user. (See Chapter 16 for a complete listing of the JAVAREG options.) The two most important options for registering a Java class as a COM object are /class:class_name and /clsid:{xxxxx}.

To register a new class, use the /register option. Likewise, to unregister a class, use the /unregister option. Using this set of commands, register the COMString class using the following syntax:


javareg /register /class:COMString /clsid:{0AD2C502-18A5-11d0-B4EE-444553540000}

The CLSID used when registering the Java class should always be the CLSID of the coclass defined in the ODL file. To verify that the COMString class was correctly registered, search through the Registry for the string COMString. Notice that the value of the HKEY_CLASSES_ROOT\CLSID\0AD2C502-18A5-11d0-B4EE-444553540000\InprocServer32 Registry key is MSJAVA.DLL. This is the in-process server for the Microsoft Virtual Machine. This value will remain the same for all Java classes registered as COM objects.

Calling the COM Object from a Java Applet

Now that a COM object has been created in Java, you must test it out to ensure that things are working the way they are supposed to. The remainder of the chapter demonstrates thoroughly testing the COM object using a Java applet and a Visual Basic application. Each of these applications looks virtually identical; the only difference is that one of them is written in Java and runs in a Web browser, whereas the other one is written in Visual Basic and runs as a standalone Windows application. What these applications have in common is that they both will call the same COM object: CString.

To demonstrate that the Java/COM object actually works as promised, five methods will be called and results will be displayed to the screen:

To call a COM object from Java, you will have to reuse the knowledge gained in Chapter 20. However, the only tricky material covered in that chapter involved registering the COM object and generating the Java class files from the COM object's type library. In the case of the COMString class, both operations have already been accomplished. Therefore, you are free to dive right in and begin building a Java applet to test the five candidate methods. This applet simply needs to import the CString classes using the following statement:


import example21.*;

The source code for the Java tester applet is shown in Listing 21.6. The interesting portion of this sample program occurs during the action() method, where the contents of the text components are retrieved based on button-push events. The text retrieved is passed to one of the COM object's member methods. The output from these methods is displayed in the text boxes on the right side.


Listing 21.6. The Java/COM tester applet.

//******************************************************************************

// COMTester.java:  Applet

//

//******************************************************************************

import java.applet.*;

import java.awt.*;

import example21.*;

import COMString;

import COMTesterFrame;



//==============================================================================

// Main Class for applet COMTester

//

//==============================================================================

public class COMTester extends Applet

{

  // STANDALONE APPLICATION SUPPORT:

  // m_fStandAlone will be set to true if applet is run standalone

  //--------------------------------------------------------------------------

  boolean m_fStandAlone = false;

  TextField LengthL, LengthR, LowerL, LowerR, TrimL, TrimR, EqualsTL, EqualsBL,

    EqualsR, StartsTL, StartsBL, StartsR;

  Button bLength, bLower, bTrim, bEquals, bStarts;

  IString StringObj;





  // STANDALONE APPLICATION SUPPORT

  // The main() method acts as the applet's entry point when it is run

  // as a standalone application. It is ignored if the applet is run from

  // within an HTML page.

  //--------------------------------------------------------------------------

  public static void main(String args[])

  {

    // Create Toplevel Window to contain applet COMTester

    //----------------------------------------------------------------------

    COMTesterFrame frame = new COMTesterFrame("COMTester");

    // Must show Frame before we size it so insets() will return valid values

    //----------------------------------------------------------------------

    frame.show();

    frame.hide();

    frame.resize(frame.insets().left + frame.insets().right  + 480,

    frame.insets().top  + frame.insets().bottom + 200);



    // The following code starts the applet running within the frame window.

    // It also calls GetParameters() to retrieve parameter values from the

    // command line, and sets m_fStandAlone to true to prevent init() from

    // trying to get them from the HTML page.

    //----------------------------------------------------------------------

    COMTester applet_COMTester = new COMTester();



    frame.add("Center", applet_COMTester);

    applet_COMTester.m_fStandAlone = true;

    applet_COMTester.init();

    applet_COMTester.start();

    frame.show();

  }



  // COMTester Class Constructor

  //--------------------------------------------------------------------------

  public COMTester()

  {

    StringObj = new COMString();

    LengthL = new TextField(25);

    LengthR = new TextField(25);

    LowerL  = new TextField(25);

    LowerR  = new TextField(25);

    TrimL   = new TextField(25);

    TrimR   = new TextField(25);

    EqualsTL = new TextField(25);

    EqualsBL = new TextField(25);

    EqualsR = new TextField(25);

    StartsTL = new TextField(25);

    StartsBL = new TextField(25);

    StartsR = new TextField(25);

    bLength = new Button("Length ->");

    bLower = new Button("ToLowerCase ->");

    bTrim = new Button("Trim ->");

    bEquals = new Button("Equals ->");

    bStarts = new Button("StartsWith ->");

  }



  // APPLET INFO SUPPORT:

  // The getAppletInfo() method returns a string describing the applet's

  // author, copyright date, or miscellaneous information.

  //--------------------------------------------------------------------------

  public String getAppletInfo()

  {

    return "Name: COMTester\r\n" +

         "Author: Bryan Morgan\r\n" +

         "Created with Microsoft Visual J++ Version 1.0";

  }



  public void init()

  {

    Panel p1, p2, p3, p4, p5, p6, p7;

    resize(480, 200);

    setLayout(new GridLayout(7, 1));

    p1 = new Panel(); add(p1); p1.setLayout(new FlowLayout(FlowLayout.LEFT));

    p2 = new Panel(); add(p2); p2.setLayout(new FlowLayout(FlowLayout.LEFT));

    p3 = new Panel(); add(p3); p3.setLayout(new FlowLayout(FlowLayout.LEFT));

    p4 = new Panel(); add(p4); p4.setLayout(new FlowLayout(FlowLayout.LEFT));

    p5 = new Panel(); add(p5); p5.setLayout(new FlowLayout(FlowLayout.LEFT));

    p6 = new Panel(); add(p6); p6.setLayout(new FlowLayout(FlowLayout.LEFT));

    p7 = new Panel(); add(p7); p7.setLayout(new FlowLayout(FlowLayout.LEFT));



    p1.add(LengthL);

    p1.add(bLength);

    p1.add(LengthR);

    p2.add(LowerL);

    p2.add(bLower);

    p2.add(LowerR);

    p3.add(TrimL);

    p3.add(bTrim);

    p3.add(TrimR);

    p4.add(EqualsTL);

    p4.add(bEquals);

    p4.add(EqualsR);

    p5.add(EqualsBL);

    p6.add(StartsTL);

    p6.add(bStarts);

    p6.add(StartsR);

    p7.add(StartsBL);

    LengthL.reshape(10, 10, 75, 25);

    bLength.reshape(95, 10, 75, 25);

    LengthR.reshape(180, 10, 75, 25);

  }



  public boolean action(Event evt, Object arg)

  {

    String str1, str2;

    String result;

    int strlen;

    boolean streq;



    if (evt.target.equals(bLength))

    {

      str1 = LengthL.getText();

      strlen = StringObj.Length(str1);

      LengthR.setText(StringObj.IntToStr(strlen));

    }

    else if (evt.target.equals(bLower))

    {

      LowerR.setText(StringObj.ToLowerCase(LowerL.getText()));

    }

    else if (evt.target.equals(bTrim))

    {

      TrimR.setText(StringObj.Trim(TrimL.getText()));

    }

    else if (evt.target.equals(bEquals))

    {

      str1 = EqualsTL.getText();

      str2 = EqualsBL.getText();

      streq = StringObj.EqualsIgnoreCase(str1, str2);

      EqualsR.setText(StringObj.BoolToStr(streq));

    }

    else if (evt.target.equals(bStarts))

    {

      str1 = StartsTL.getText();

      str2 = StartsBL.getText();

      streq = StringObj.StartsWith(str1, str2);

      StartsR.setText(StringObj.BoolToStr(streq));

    }

    return true;

  }

}


The following COMString methods are called in Listing 21.6:

Figure 21.1 illustrates what the applet looks like in the Internet Explorer 3.0 Web browser.

Figure 21.1 : The tester applet in Internet Explorer 3.0.

This example demonstrates that it is possible to call a COM object from within a Java application. However, one of COM's best features is its language independence. The example in the following section builds a sample application that is very similar to the first except that it is created in Visual Basic.

Calling the COM Object from Visual Basic

For a Visual Basic application to retrieve a Java/COM interface and call that interface's methods, two steps are required. First, the Visual Basic application must declare a variable whose data type is defined to be the interface name. For instance, in the previous sections you created a COM interface named IString. To use this interface in Visual Basic, you would declare a variable using the following syntax:


Dim StringObj As IString

Continuing with the objects created in the previous example, to initialize the StringObj variable, you would issue the following statement:


Set StringObj = New CString

From that point on, the interface methods can be called using the standard Visual Basic dot notation (.) syntax.

NOTE
Make sure to put the COMSTRING.CLASS Java class file somewhere in the Java class path before running the Visual Basic application. The class path directories can be determined by examining the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Java VM\Classpath Registry key. The application will also work if the Java class file is located in the \trustlib directory with the generated Cstring and Istring COM stub classes

If you plan to build a Visual Basic application from scratch that calls an existing COM object, add a reference to that object's type library by selecting the Tools | References menu option and then selecting the COM object's type library in the ensuing dialog. When this is done, Visual Basic adds a CLSID and type library name internally in the project. When this is done, you can create and use an object of that type freely within the program.

The StringObj parameter can be initialized by issuing the following command in the Form_Load() method:


Set StringObj = New CString

When this variable has been initialized, it is simply a matter of calling the COMString object's methods and returning their results to the screen. Listing 21.7 contains a partial code listing of the COMServer Visual Basic application. It shows the syntax used to call the various COM functions from Visual Basic.


Listing 21.7. Visual Basic code used to call COM interface methods.

Private Sub bEquals_Click()

  EqualsR.Text = StringObj.EqualsIgnoreCase(EqualsTL.Text, EqualsBL.Text)

End Sub



Private Sub bLength_Click()

  Dim length As Integer

  length = StringObj.length(LengthL.Text)

  LengthR.Text = length

End Sub

Private Sub bLower_Click()

  LowerR.Text = StringObj.ToLowerCase(LowerL.Text)

End Sub



Private Sub bStarts_Click()

  StartsR.Text = StringObj.StartsWith(StartsTL.Text, StartsBL.Text)

End Sub

Private Sub bTrim_Click()

  TrimR.Text = StringObj.Trim(TrimL.Text)

End Sub


Figure 21.2 shows the Visual Basic application running with outputs sent from the COMString Java/COM object.

Figure 21.2 : The Visual Basic COMTester application.

Although the examples here use only Java and Visual Basic, similar functionality is possible using languages such as Delphi or C++. Keep in mind that to call Java COM objects from a Visual Basic (or other language) application, you must have the Microsoft Virtual Machine installed on the local system. It then follows that for the Virtual Machine to be installed, Microsoft's Internet Explorer browser must also be installed. The reality of the situation now is that few people will probably choose to implement a COM object using Java because Java is an interpreted language. To avoid this performance hit, COM objects will probably continue to be built using C or C++ until Java can be compiled to machine code in future versions of Visual J++.

Summary

Using tools such as JavaTLB and JAVAREG, the Visual J++ developer can develop COM objects that can then be used by programmers using other languages such as Visual Basic, Delphi, and C++. The ODL is used to build a COM class definition. After the definition has been completed, the ODL source file can be compiled to create a COM type library using the MKTYPLIB tool included with Visual J++. When the type library has been built, Java classes can be generated using the JavaTLB tool. The Java code for the COM object can be written to utilize COM without adding any new Microsoft-proprietary extensions to the Java language. A COM object can be created for use within an application simply by using the new keyword. Methods and properties within that object can then be utilized using standard Java syntax. After the class has been successfully built using Visual J++, it can be registered in the Windows Registry using JAVAREG. When the object has been registered, it can be used by any Windows application that can dynamically allocate and create a COM object.