Example 1 - Simple COM HelloWorld Server and Java Driver - Kishore J Doshi


Introduction:

The following example is here as proof of principle. I created this paralled example with the assistance of the SDK examples. Code and screenshots of the example, along with an explanation of how it was created follows. This first example is a simple Java application that calls the HelloWorld method on the HelloWorld COM server, when the user presses the Call COM Method button. The String returned from the server is displayed on the Frame in Label below the Clear Display button.

Section Links
Steps for compiling and running Example 1
Screenshots for Example 1
Back to Section Introduction

Download All files Associated with this Project in two ZIP files
All Java Source Code
COM HelloServer Visual Studio Project



Creating the COM Server:

Creating this server is simple and I will not get into the details here since that is not the purpose, but here are the steps:

  • Using Microsoft Visual C++ 5.0 - run the ATLCOM Wizard and create an Automation Object

  • Modify the IDL file, and add the HelloWorld interface to the object, the interface has one method. Look at the IDL file here.
  • The HelloWorld method in IDL: HRESULT HelloWorld([out, retval]BSTR * helloString)

  • Provide an C++ implementation for HelloWorld. Look at it here.

  • Build the .exe for the server. The compiler will register the server and create the HelloServer.tbl file we need to create the Java class for the server.

  • The user does not need to create the server project from scratch. I provied the the files for the Visual Studio project in the zip file here.

  • The user only needs to compile the project and let the compiler register the server for you




  • Creating the Java Interfaces and Classes:

    Here we are going to use the jActiveX tool to create our Java classes for this COM object. The file HelloServer.tbl is a binary file that contains all the information about what interfaces the object implements. The tool will create all the Java classes for the interface and place them in a package. We use the following syntax which tells the jActiveX tool to dump the HelloServer.tbl file and create the classes representing the interface

    Syntax: jactivex /javatlb /d (the directory for the package) HelloServer.tlb

    The /javatlb option tells the tool we want to create Java classes for the interfaces in the .tlb file. /d option allows us to specify the directory where the packages go. The following .java files are created:

  • ComHelloServer.java - The object that represents COM implemetation of the IComHelloServer interface

  • IComHelloServer.java - The Java representation of the interface. We make our calls on the COM object through the interface, not the actual object

  • IComHelloServerDefault


  • IComHelloServer.java:

    Let's take a look at the Java version of the interface

    package helloserver;

    import com.ms.com.*;
    import com.ms.com.IUnknown;
    import com.ms.com.Variant;

    // Dual interface IComHelloServer
    /** @com.interface(iid=30A59B60-D368-11D2-890B-000000000000, thread=AUTO, type=DUAL) */
    public interface IComHelloServer extends IUnknown
    {
    /** @com.method(vtoffset=4, dispid=1610743808, type=METHOD, name="HelloWorld"),
    @com.parameters([type=STRING] return) */
    public String HelloWorld();

    public static final com.ms.com._Guid iid = new com.ms.com._Guid((int)0x30a59b60, (short)0xd368, (short)0x11d2,
    (byte)0x89, (byte)0xb, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0);
    }


    First we see that IComHelloServer extends IUnknown. All interfaces that describe COM objects extend IUnknown. This provides the QueryInterface, AddRef and Release methods. These methods are already implemented in the Java version of the IUnknown interface. Through the Microsoft VM these methods actually tie to the HelloServer com object and are not exposed to the Developer. When the developer creates and instance of the class that implements this interface, the VM will call CoCreateInstance, connect to the COM object, call AddRef and return the pointer to the interface so that we can work with it in Java

    If we look above the interface declaration, we see the @com.interface directive in the comments. This directive tells the compiler and the VM that this is a Java interface marked as a COM interface. It also tells the id of the COM interface this Java interface represents. When we compiled and registered the HelloWorld server. The interface was registered under this clsid. The thread=Auto tells the VM that it should marshal any calls between the thread that this object is on and any other threads it's methods are called from.

    Just above the method, we see two more compiler directives. @com.method. The VM sets up a virtual function table for each COM object. Since all COM objects implement IUnknown, they automatically have three methods in their vtable, QueryInterface, AddRef, Release. Any methods defined in the interface will be below these three in the vtable. Thus, HelloWorld is at offset 4 in the vtable. The other attributes are dispid, which is a unique identifer for the method, it's name, and type. We don't have to only have methods in the vtable, we can have functions which can manipulate the object properties.

    Now we get to the method. public String HelloWorld() It takes no parameters and returns a String, We saw in the idl for HelloServer, the out parameter was a BSTR*. In the mapping of MIDL types to Java types, a BSTR* becomes a java String.

    Finnally, we define a GUID object for the iid.



    ComHelloServer.java:

    Let's take a look at the Java version of the coclass

    package helloserver;

    import com.ms.com.*;
    import com.ms.com.IUnknown;
    import com.ms.com.Variant;

    /** @com.class(classid=30A59B61-D368-11D2-890B-000000000000,DynamicCasts) */
    public class ComHelloServer implements IUnknown,com.ms.com.NoAutoScripting,helloserver.IComHelloServer
    {
    /** @com.method()
    @hidden */
    public native String HelloWorld();

    public static final com.ms.com._Guid clsid = new com.ms.com._Guid((int)0x30a59b61, (short)0xd368, (short)0x11d2,
    (byte)0x89, (byte)0xb, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0);
    }


    Being a COM coclass, ComHelloServer implements the IUnknown interface and the IComHelloServer interface.
    Above the class declaration we see the @com.class directive. This tells the VM and the compiler the clsid of the COM object. What does dynamic casts mean? It means that we can cast this class to other COM interfaces not directly implemented by this class. The VM will call QueryInterface on the actual COM object to get the interface desired.

    We see the declaration of the method, this time it's declared as native since it is not defined in any Java classes but it tells the VM to make a call into the server object. It is only here to satisfy Java syntax requirements. It's hidden because it is not defined here, but in the COM object.

    Finnally, we define a GUID object for the iid.



    Client.java:

    Let's take a look at the Client program that uses this class: Client.java
    This program has a single thread of execution. Let's look at the Constructor:

    public Client(){
    com.ms.com.ComLib.declareMessagePumpThread(); (1)
    loopThreadID = com.ms.win32.Kernel32.GetCurrentThreadId(); (2)
    //Inside the virtual machine this invokes a call to CoCreateInstance
    //starts up the program HelloServer.exe and gives me reference to the interface
    theServer = (IComHelloServer) new ComHelloServer(); (3)
    //Give the Server some time to come up
    try{
    Thread.sleep(10); (4)
    }catch(Exception ii) {}

    startGUI(); (5)
    //setup a windows message loop for the thread, we want to catch the WM_QUIT message
    com.ms.win32.MSG msg = new com.ms.win32.MSG();
    while (com.ms.win32.User32.GetMessage(msg, 0, 0, 0)) { (6)
    com.ms.win32.User32.TranslateMessage(msg);
    if (msg.message==com.ms.win32.win.WM_QUIT)
    break;
    com.ms.win32.User32.DispatchMessage(msg);
    }

    //When were done, we release it
    com.ms.com.ComLib.release(theServer); (7)
    //Give the VM some time to release the Server
    try{
    Thread.sleep(1000); (8)
    }catch(Exception ii){}
    System.exit(0); (9)
    }


  • Line 1 - Basically designates that this thread will handle Windows Messages

  • Line 2 - Another Win32 call that we can make thanks to the Microsoft VM, the ThreadID, we need to know this if we want to send messages to this thread

  • Line 3 - This is the CoCreateInstance call in disguise to startup the out of proc server.

  • Line 4 - We tell the main thread to sleep while we wait for the server to start up.

  • Line 5 - Here we call the private function startGUI, this sets up the frame and the AWT components.

  • Line 6 - Again we are taking advantage of using Win32 in our Java apps, we set up a Windows message loop.

  • Line 7 - The thread recieves a WM_QUIT message. We call release() to disconnect from the Server

  • Line 8 - We give some time to disconnect from the object

  • Line 9 - We exit the program


  • Notes on Client Constructor

    More on Line 3 - When we create a new instance of the COM object ComHelloServer, the VM knows from the directives that this object is actually the COM object. Searches the registry for the clsid to find the COM server executable.
    This is the key point - The VM returns a pointer to the interface for this COM object, just like if we had made the CoCreateInstance call our own. This means that if we want to make any function calls on the object ComHelloServer in our Java program, we must do it through the Java interface for this object, IComHelloServer. Therefor we cast the reference from ComHelloServer to IComHelloServer.

    More on Line 5 - Note that we start up the AWT GUI on the same thread as the calls to the COM object. According to Microsoft, this is not a good idea because the GUI updates will be blocked while calls are made on the COM object. It is better to create each COM object on a separate thread. The second example works this way.

    More on Line 6 - Since we declared this thread as a message pump, we need a message loop. We are using the package com.ms.win32. This contains the Java representations of Win32 API's. So we catch any Win32 messages sent to this thread, we only process the WM_QUIT message, which ends the message loop.

    More on Line 7 - We have recieved the quit message, now we call release, which is one of the three IUnknown methods already defined for us. This will disconnect us from the COM object and lower it's reference count by one. If the reference count of the COM object is zero, it goes away(basic COM).




    Client.java:

    Let's look at some of the listeners, this is where the interesting code is. I will not talk much about the Frame and the Buttons since that is Java background we assume the reader has. The screenshots are here.

    CallComFunction - Listener

    The class CallComFunction implements the ActionListener interface. We attach this listener to the button labeled "Call COM Method"

    class CallComFunction implements ActionListener{
    IComHelloServer theServer;
    Frame theWindow;
    Label serverResponse;

    public CallComFunction(IComHelloServer theServer, Label serverResponse, Frame theWindow){
    this.theServer = theServer;
    this.theWindow = theWindow;
    this.serverResponse = serverResponse;
    }

    public void actionPerformed(ActionEvent evt){
    String response = theServer.HelloWorld(); //Make a call on the COM interface (1)
    serverResponse.setText(response); (2)
    theWindow.validate(); //Let's redo the layout to catch the update (3)
    }
    }


    Let's start by looking at the declared variables. We need to hold the reference to the COM object interface that we initialized in the constructor, IComHelloServer. It also holds a reference to the Frame and the Label components. We use the Label to display the string we recieve from the COM server, and we use the Frame reference so we can call validate() and update the frame.

    The actionPerformed function is the most interesting, let's look at it line by line:
  • Line 1: Here we actually make the call to the COM object through the HelloWorld() method on the IComHelloServer interface.

  • Line 2: We call the setText() method for the Label component and pass it the String we get from the COM server.

  • Line 3: We validate() the entire Frame which validates all components, including our Label.


  • WindowHandler - Listener

    The class WindowHandler listens for the window event that represents the user closing the window. This is where we take advantage of the fact that our thread is a message loop handler. All we have to do is post a Win32 WM_QUIT message to the main thread. This is caught by the loop described in the Client constructor, then destroys the Frame window. The remainder of the code in the constructor will close the reference to the COM object, and the program terminates.

    class WindowHandler extends WindowAdapter{
    Frame theWindow;
    int threadID;

    public WindowHandler(Frame theWindow, int threadID){
    this.theWindow = theWindow;
    this.threadID = threadID;
    }

    public void windowClosing(WindowEvent evt){
    theWindow.dispose();(1)
    com.ms.win32.User32.PostThreadMessage(threadID, com.ms.win32.win.WM_QUIT, 0, 0);(2)
    }
    }


  • Line 1: We destroy the Frame window with the dispose() function.

  • Line 2: We make a Win32 API call and post a WM_QUIT message to the message loop in the main thread>