Paul Johns
Microsoft Developer Network
August 1998
Summary: Discusses the creation of Microsoft® Visual J++™ version 6.0 COM objects and Microsoft ActiveX® controls based on existing IDL files, type libraries, or COM objects. (18 printed pages) Covers:
Why use Visual J++? Because Java is a great language to work in. (Even Microsoft executives testify to this.) It's more powerful than Microsoft Visual Basic®, but not as hard to learn and use as Microsoft Visual C++®. And with the powerful features of the new Windows Foundation Classes (WFC) in Visual J++ 6.0, you can produce great programs for the Windows platform in record time, taking full advantage of Windows, Java, and even dynamic HTML (DHTML).
Don't forget, too, that making a COM object out of Java classes you've already written is a great way to allow programs written in any COM-compatible language, even scripting languages, to use your Java classes. That makes your Java code that much more valuable, because it can be used from languages other than Java.
Every silver lining, however, has its cloud. And there are a couple of reasons you might choose to use another language. First, although Java COM dynamic-link libraries (DLLs) are small, comparable to Visual Basic or Microsoft Foundation Classes (MFC) COM DLLs, they require additional run-time support in the form of a compatible version of the Microsoft virtual machine (VM) and classes. You may have to figure out how to make sure that a compatible VM is installed on your users' machines. But if your users have plenty of memory and have the VM installed, this isn't a problem at all.
The need for the virtual machine causes the other disadvantage: Java, despite our great just-in-time compiler, is still somewhat slower than a control written in C++ or C++ with Active Template Library (ATL). For many applications, the speed disadvantage isn't significant. How will you know for your application? The only way to know for sure is to test.
If you're using the Visual J++ Technology Preview 2 (TP2), which shipped with Visual Studio 6.0, you'll want to update to the released version of Visual J++. Be sure to update MSDN to the Visual Studio® 6.0 version so you have the latest documentation. This is especially important for COM programming with Visual J++ because much of the Java/COM documentation was "under construction" at the time TP2 was published and thus not in the TP2 release.
Visual J++ 1.1, the previous version of Visual J++, only allowed you to create COM objects (also known as Automation objects)—these are objects without a visible user interface. Thanks to the new Windows Foundation Classes (WFC), Visual J++ 6.0 also allows you to create ActiveX controls—that is, controls with complete user interfaces.
To create a COM object or an ActiveX control, select New Project on the File menu. You'll get the New Project dialog box. Open up the directory tree to Visual J++ Projects/Components, as shown in Figure 1. Here, you have a choice of whether to create a COM DLL (without a user interface) or a control (with a user interface).
Figure 1. Creating a COM object or an ActiveX control
Set the project name and location as you like, and then double-click the COM DLL or Control icon. Visual J++ will create a skeleton project for you. (For purposes of this article, we'll only create COM DLLs, which correspond to COM components.)
Once the project is created, check out its properties by right-clicking the Project Explorer window and selecting "<project name> Properties…" or by selecting "<project name> Properties…" from the Project menu. This brings up the Project Properties dialog box. There's a lot of cool functionality you can access here, including information about how to make COM classes out of your Java classes (obviously, we'll be using this one!) and even the output format. The default output format for a COM DLL project is a COM DLL; but if we're going to deploy our object over the Internet, we might want to change the output format to a CAB archive.
If you ever did COM objects in Visual J++ 1.1, you'll remember that the output was always a set of .class files and that it was your responsibility to figure out how to get the .class files onto the user's machine and make the proper registry entries. With Visual J++ 6.0, you can produce a DLL directly that contains everything you need in one file—even the type library necessary for self-registration.
There are a number of other options available here—you can produce just a set of .class files by clearing the Enable Packaging check box, or you can, for appropriate projects, package your project as a Windows EXE, a self-extracting setup EXE, or even a Zip archive.
When building a new COM object, you basically have three options: creating the object from scratch, creating it from an existing Java class, or creating it from an existing type library.
The released Visual J++ documentation does a good job explaining how to create a brand-new COM object and how to modify an existing Java class so you expose it as a COM object. For more information, see the Visual J++ documentation on the MSDN Library CD, in particular the section on Building and Importing COM Objects in the Visual J++ Programmer's Guide.
If you're writing the object from scratch, select the COM DLL project type when you create a new project, as just described, and then add the methods you want. Visual J++ will automatically build the type library for you based on your Java code.
Or, if you're making an existing Java class a COM object, you define the existing class as a COM class using the COM Classes tab of the Project Properties dialog box. All you do is select the classes you'd like to make COM classes. Visual J++ takes care of adding the special @com.register directive comment to your code for you. You can also add this directive by hand, as described in the documentation referenced above. Finally, you change the packaging option to COM DLL and rebuild. Note that you may want to copy your project source files to a new project before you do this.
So, if you're creating a new object with a new interface, the steps are quite simple. But what if you're creating a new Java implementation of an existing interface? Then the documentation fails you—and things get a little tricky. Read on.
Why would you want to use an existing type library as the basis for your Java class? The major reason is that you might want client code written to that type library to be able to use your new Visual J++ COM object without the need for the client code to be modified at all. One example of a situation in which you might want to do this is if your Visual J++ implementation is to replace an implementation in some other language. Another is if you want to provide an alternate implementation of another COM object for which you have the type library. (If you're providing an alternate implementation, you'll want to change the CLSID in the type library—more on this later.)
In other words, by implementing an existing type library, your new object can be used by existing client code.
By the way, Visual Basic doesn't give you the ability to implement an existing COM class interface directly—that's one of the advantages of using Visual J++. (C++ has always had this capability.)
Before you can begin, you'll have to find an existing type library. They exist in three forms: as Interface Description Language (IDL) source files, as stand-alone TLB files, or built into an existing COM object (DLL, OCX, or EXE).
Visual J++ needs a file of one of the last two forms (TLB, DLL, OCX, or EXE) as input to create your implementation class. But that's not all: You'll also need to have a TLB file so Visual J++ can build the type library into your COM DLL. So whatever you do, you're going to need the TLB file—no ifs, ands, or buts about it.
If you're really lucky, you'll have a TLB file to start with. If so, just copy it into your Visual J++ project directory. Visual J++ will automatically add it to your COM DLL project and use it correctly. But if you have an IDL file and are building components in more than one language, you may want to use the IDL file as your starting point instead so you have a single definition for the interface.
Life is not much more difficult if you start with an IDL file. You'll need to generate the TLB, but that's easy: Just run the MIDL compiler on your IDL file. To get MIDL to generate only the TLB file, you'll need to send all of the other output files to the NUL file. For instance:
midl /proxy nul /header nul /iid nul /dlldata nul /win32 beepcnt.idl
…would produce a TLB file only. All of the options except /win32 direct output to a file that would have been created to NUL, thereby eliminating it. Because the input file is beepcnt.idl, the output file will be beepcnt.tlb.
Be sure, of course, to use an up-to-date version of MIDL—the one that comes with Visual C++ 6.0 and with Visual Studio 6.0 works fine.
Unfortunately, MIDL doesn't ship with Visual J++. If you don't have Visual C++ 6.0 or Visual Studio 6.0, you can get MIDL and the necessary header files by downloading the Microsoft Platform SDK Build Environment at http://msdn.microsoft.com/developer/sdk/default.asp or from CD if you have a MSDN Professional or Universal Subscription.
If you happen to have an Object Description Language (ODL) file rather than an IDL, you can compile it to a TLB by adding the /mktyplib203 option to the MIDL command line.
Even if you have a TLB file, you may want to use an IDL file as your starting point. That way, you have ONE source (the IDL) that's used by Java and any other language (such as C++) that implements the object interface.
It may be tempting to make a custom prebuild rule to rebuild the type library automatically each time, but don't. If the IDL (and, therefore, the type library) changes, you'll have to rework the Java class methods by hand. There's no way to make the changes automatically. So automatically building a type library that isn't the same as the Java class definition would be a bad idea.
Sometimes, though, you don't have any source code for the object (no IDL), nor do you have a TLB file. Perhaps you're doing an implementation of an object for which you only have the binary files—the DLL, OCX, or EXE. What do you do?
Many COM object modules have type libraries built into them. You can use the OLE Viewer (oleview.exe) to look at the type library for your object. The OLE Viewer format for displaying the object's type library is IDL. So you simply select the IDL (pressing Ctrl+A while the focus is in the IDL window will select it all), copy the code to the clipboard, paste it into "Visual" Notepad or your favorite editor, and save it as an IDL file.
Then compile the IDL file with the MIDL compiler as just described. You'll have your TLB file, and life will be beautiful.
Or at least that's how it is in the movies—but not in real life. Unfortunately, there are slight differences between the IDL displayed by the OLE Viewer and the correct IDL syntax; you may need to fiddle with the IDL so MIDL won't riddle you with compiler errors. (Look at the error listing and move as little as possible.)
The most common type of error has to do with enumerations. These need to be before the library block, but OLE Viewer's listing puts them after the library block. Moving the typedefs inside the library blocks allows you to whittle the MIDL riddles for your IDL down to nothing—and produce the type library you need, rhymes not included.
It's a good idea to generate a Java class from both the original DLL and the newly generated type library to make sure there are no significant differences. The WinDiff tool supplied with Microsoft language products is ideal for this.
If you have several files from which to choose, use an IDL file as your starting point, provided you can compile it to a type library. If none are available, use a TLB—and finally, if you have no choice, create an IDL file out of an existing object and diddle with the IDL until it MIDLs without piddling any more of your time.
If you have (or create) an IDL file, you'll note that the object and all its interfaces all have GUIDs as their IDs. (Note that if you don't compile your own IDL file, you can't change the GUIDs.) Unless you change the interface, you should not change the interface ID (IID) GUID.
The decision about whether to change the GUID for the class ID (CLSID)—the one on the "coclass" declaration—is a little more complicated.
If you change the CLSID, clients that want to refer to your object by CLSID will have to use the new CLSID to refer to your object. (If your clients refer to your object by PROGID, the CLSID doesn't matter.)
If you don't mind having both versions of the object on a user's system, changing the CLSID is an advantage. Old clients can use the old version of the object, and new clients use the new version. In other words, each client uses the version of the object with which it was tested. Doing this should improve reliability of your clients. If you leave the CLSID the same, you can replace the old object with the new object. Both old and new clients will use the new object. You obviously have a great responsibility to make sure you don't break old clients if you do this.
Our example is a very simple object—it has one method and one property. The method is called Beep and it simply beeps the speaker the number of times specified by the Count property. The IDL for this is easy:
[id(1), helpstring("method Beep")] HRESULT Beep();
[propget, id(2), helpstring("property Count")]
HRESULT Count([out, retval] long *pVal);
[propput, id(2), helpstring("property Count")]
HRESULT Count([in] long newVal);
View the entire IDL file.
This IDL is from a COM object created in ATL. The code for these methods in C++ is also easy:
// "long cBeeps" in class declaration
CBeepCount::CBeepCount() : cBeeps(1) { }
STDMETHODIMP CBeepCount::Beep()
{
for (long i = 0; i < cBeeps; i++)
MessageBeep((UINT)-1);
return S_OK;
}
STDMETHODIMP CBeepCount::get_Count(long *pVal)
{
*pVal = cBeeps;
return S_OK;
}
STDMETHODIMP CBeepCount::put_Count(long newVal)
{
cBeeps = newVal; // note lack of error-checking
return S_OK;
}
View the entire header file and C++ file.
Note that we've initialized the beep count to 1. We'll initialize it to 2 in the J++ COM object so it'll be easy to tell whether we're using the ATL/C++ object or the Visual J++ object.
For our example, we'll create a new Visual J++ project. First, create a new COM DLL project as described in the section "Types of Components You Can Create" earlier in this document. By default, the project will have COM DLL packaging, which is what we want. Note that the project name you pick will be the name of the COM object.
Visual J++ will create a Java class called "Class1.java." Delete this class by right-clicking it in the Project Explorer View and selecting Delete from the menu. This will delete the file from the project and from your computer.
As just described, you need a type library as your starting point. Because we have the IDL file available from the ATL project, we'll just use that and compile it to a TLB file using the MIDL command line shown earlier.
What if we didn't have the IDL or a type library? Perhaps we're duplicating the interface of an object that we don't have source for—all we have is a self-registering DLL. If all we've got is a self-registering DLL, we're still okay—we just use OLE Viewer to view the object's type library, copy the IDL code and paste it into a new IDL file, and compile the IDL file into a type library. We might have to rearrange the IDL some, but that's fairly easy.
How you get the TLB file doesn't matter. Just copy it into your project directory. You can add it to your project by clicking the Show all files button in the Project Explorer View, and then right-clicking the file you want to add and selecting Add to Project. It's not absolutely necessary to add it to the project, but I like to do it for consistency. It is absolutely necessary to copy the TLB file to the project directory. I like to copy the IDL file and the batch file I use to compile the IDL file as well.
In this case, I did not change the class ID GUID for the object because I wanted to be able to replace the ATL component with the Visual J++ component without recompiling clients.
Next, use the COM Classes property page on the project Properties dialog box to specify the type library to use. First, select the Use existing Type Library option button, and then click the Select button.
Figure 2. Choosing an existing type library
On the dialog box that comes up, click the Browse button, browse to your project directory, and double-click the type library file name. When you've done that, the dialog should look like this.
Figure 3. Installing a .tlb file
Click OK on both dialog boxes. Visual J++ will create a new subproject package and put three files in that package. After the creation, the Project Explorer looks like the following.
Figure 4. Creating a subproject package
The files that begin with "I" are dual definitions of the interface used by this project. IBeepCount defines the interface; IBeepCountDefault extends IBeepCount. BeepCount.java, which extends IBeepCountDefault, is the implementation file for your project. It looks like this:
/** @com.register(clsid=4F745310-3943-11D2-A2B5-00C04F8EE2AF,
typelib=4F745303-3943-11D2-A2B5-00C04F8EE2AF, version="1.0",
description="BeepCount Class")*/
public class BeepCount implements
IUnknown,com.ms.com.NoAutoScripting,beepcnt.IBeepCountDefault
{
public void Beep() {
throw new com.ms.com.ComFailException(0x80004001); // E_NOTIMPL
}
public int getCount() {
throw new com.ms.com.ComFailException(0x80004001); // E_NOTIMPL
}
public void setCount(int pVal) {
throw new com.ms.com.ComFailException(0x80004001); // E_NOTIMPL
}
}
Compare the function parameters and return values to the C++ code and the IDL code. They're quite similar. By default, all the methods return an HRESULT of E_NOTIMPL, indicating that they're not yet implemented. In Visual J++, you throw an exception to return an HRESULT other than S_OK.
Next, we replace the throw statements in each of the methods with the actual code for the method.
Standard Java has no way to beep the speaker unless you have a visual component object derived from the Component class. (The code to beep would be "yourComponent.getToolkit().beep();".)
Because we have no visual components, Java gives us no way to beep. Okay, we could derive a dummy class from Component, create an invisible object of that class, and call getToolkit().beep() on that object, but that seems like a lot of work to beep the speaker. However, the situation is better than in JDK 1.0: In that version, there was no way to make any sound at all except from applets.
However, because we know we're running on Windows, it's easy to use Microsoft J/Direct™ to get access to the Windows MessageBeep function. To do this, use the J/Direct call builder (View:Other Windows:J/Direct Call Builder), change the target to the class you want to call Windows from, select the function you want, and click Copy To Target, as shown here.
Figure 5. Using J/Direct to call the MessageBeep function
The J/Direct call builder will insert the following declaration in our class:
/**
* @dll.import("USER32",auto)
*/
public static native boolean MessageBeep(int uType);
MessageBeep is now a member of our class, so we can call it any time we like.
The rest is very simple:
int cBeeps = 2;
public void Beep() {
for (int i = 0; i < cBeeps; i++)
MessageBeep(-1);
}
public int getCount() {
return cBeeps;
}
public void setCount(int pVal) {
cBeeps = pVal; // note lack of error-checking
}
View the complete Java code.
Note that we set the default count to two, so it's easy to distinguish between the Java and C++ versions of this object by looking at the initial value of the Count property.
To build the project, just select Build on the Build menu. You'll be shocked at how quickly the project builds. And it also registers the COM object, so at this point you're ready to go.
The ActiveX Control Test Container has improved since Visual Studio version 5.0, but it unfortunately still doesn't allow you to test nonvisual controls. So, we fall back on our old standby: writing a quick and dirty Visual Basic program to test the control. Here's what my test application looks like. View the Visual Basic code behind the form.
Figure 6. Testing the control
When the form is initialized, the edit control is set to the value of the property. (Note that it's initially 2 here, indicating that we're using the Visual J++ version of the COM object.) When you change the count, the Visual Basic app changes the property in the object. When you click the button, the Beep method is called.
With about the same amount of work, you can do this in Visual J++. You just have to be more careful about type conversions and properties. Java doesn't do type conversions for you, and it doesn't have a concept of properties in interfaces as do Visual Basic and Visual C++, so you have to be sure to use the Get and Set functions.
It's easy to take advantage of the power, ease of use, and higher productivity of the Java language to build COM components that can be used from any language—whether you have an existing type library or want to create an entirely new object with new interfaces. Before you do, though, be sure that performance and installation won't be problems for your customers.
// BeepCnt.idl : IDL source for BeepCnt.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (BeepCnt.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(4F74530F-3943-11D2-A2B5-00C04F8EE2AF),
dual,
helpstring("IBeepCount Interface"),
pointer_default(unique)
]
interface IBeepCount : IDispatch
{
[id(1), helpstring("method Beep")] HRESULT Beep();
[propget, id(2), helpstring("property Count")]
HRESULT Count([out, retval] long *pVal);
[propput, id(2), helpstring("property Count")]
HRESULT Count([in] long newVal);
};
[
uuid(4F745303-3943-11D2-A2B5-00C04F8EE2AF),
version(1.0),
helpstring("BeepCnt 1.0 Type Library")
]
library BEEPCNTLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(4F745310-3943-11D2-A2B5-00C04F8EE2AF),
helpstring("BeepCount Class")
]
coclass BeepCount
{
[default] interface IBeepCount;
};
};
// BeepCount.h : Declaration of the CBeepCount
#ifndef __BEEPCOUNT_H_
#define __BEEPCOUNT_H_
#include "resource.h" // main symbols
/////////////////////////////////////////////////////////////////////////
////
// CBeepCount
class ATL_NO_VTABLE CBeepCount :
public CComObjectRootEx,
public CComCoClass<CBEEPCOUNT, &CLSID_BeepCount,
public IDispatchImpl<IBEEPCOUNT, &LIBID_BEEPCNTLib &IID_IBeepCount,
{
public:
CBeepCount() : cBeeps(1)
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_BEEPCOUNT)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CBeepCount)
COM_INTERFACE_ENTRY(IBeepCount)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// State
long cBeeps;
// IBeepCount
public:
STDMETHOD(get_Count)(/*[out, retval]*/ long *pVal);
STDMETHOD(put_Count)(/*[in]*/ long newVal);
STDMETHOD(Beep)();
};
#endif //__BEEPCOUNT_H_
// BeepCount.cpp : Implementation of CBeepCount
#include "stdafx.h"
#include "BeepCnt.h"
#include "BeepCount.h"
/////////////////////////////////////////////////////////////////////////
////
// CBeepCount
STDMETHODIMP CBeepCount::Beep()
{
for (long i = 0; i < cBeeps; i++)
MessageBeep((UINT)-1);
return S_OK;
}
STDMETHODIMP CBeepCount::get_Count(long *pVal)
{
*pVal = cBeeps;
return S_OK;
}
STDMETHODIMP CBeepCount::put_Count(long newVal)
{
cBeeps = newVal; // note lack of error-checking
return S_OK;
}
//
// Auto-generated using JActiveX.EXE 5.00.2918
// ("D:\Program Files\Microsoft Visual Studio\VJ98\jactivex.exe"
/javatlb /c2j /creg /xh /wfc /w /xi /X:rkc /l "C:\TEMP\jvc97.tmp"
/nologo /d "D:\Paul\ATL Examples\BeepCnt\JavaObject" "D:\Paul\ATL
Examples\BeepCnt\JavaObject\beepcnt.tlb")
//
// WARNING: Do not remove the comments that include "@com" directives.
// This source file must be compiled by a @com-aware compiler.
// If you are using the Microsoft Visual J++ compiler, you must use
// version 1.02.3920 or later. Previous versions will not issue an error
// but will not generate COM-enabled class files.
//
package beepcnt;
import com.ms.com.*;
import com.ms.com.IUnknown;
import com.ms.com.Variant;
/** @com.register(clsid=4F745310-3943-11D2-A2B5-00C04F8EE2AF,
typelib=4F745303-3943-11D2-A2B5-00C04F8EE2AF, version="1.0",
description="BeepCount Class")*/
public class BeepCount implements
IUnknown,com.ms.com.NoAutoScripting,beepcnt.IBeepCountDefault
{
int cBeeps = 2;
public void Beep() {
for (int i = 0; i < cBeeps; i++)
MessageBeep(-1);
}
public int getCount() {
return cBeeps;
}
public void setCount(int pVal) {
cBeeps = pVal; // note lack of error-checking
}
/**
* @dll.import("USER32",auto)
*/
public static native boolean MessageBeep(int uType);
}
Dim BeepCnt As BeepCount
Private Sub Beep_Click()
BeepCnt.Beep
End Sub
Private Sub CountEdit_Change()
On Error GoTo ErrorHnd
If CountEdit >= 0 Then BeepCnt.Count = CountEdit
Exit Sub
ErrorHnd:
Resume Next
End Sub
Private Sub Form_Load()
Set BeepCnt = New BeepCount
CountEdit = BeepCnt.Count
End Sub