Example 2 - DS 420 DCOM Project with a Java Control - Kishore J Doshi


Introduction:

In this example we start with two programs from the DS 420 DCOM assignment. An MFC application which sends new values to the server, and the COM server which echos the values back to the MFC application and forwards the new values to any controls attached to it through connection points. This project is based on the DCOM tutorial An ActiveX Control and DCOM Sample USING ATL by Steve Robinson and Alex Krasilshchikov.

The key change is that the ActiveX control is replaced with a MutilThreaded Java Client that is connected to the server. When new values are broadcast by the server to Java Client, it's background color changes.

Since this example assumes some familiarity with COM we will not spend much time on the MFC application and the COM server, all C++ source code for these two components is provided, the user needs only to compile the Visual Studio projects for both.

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

Download All files Associated with this Project in two ZIP files
All Java Source Code
COM Server Application Visual Studio Project
MFC Pusher Application Visual Studio Project



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 TheServer.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 to dump the TheServer.tbl file and create the classes representing the interface

Syntax: jactivex /javatlb /d (the directory for the package) TheServer.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:

  • ITheServerComObject.java - The Java representation of the COM interface for TheServerComObject

  • IMySinkID.java - The Java representation of the Sink Interface. We establish a connection point between TheServer and the Sink object in the Java client. The Sink object implements this interface

  • TheServerComObject.java - The Java representation of the COM class for TheServerComObject

  • ITheServerComObjectDefault.java

  • IMySinkIDDefault.java




  • ITheServerComObject.java:

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

    package theserver;

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

    // Dual interface ITheServerComObject
    /** @com.interface(iid=198C064D-B949-11D2-8902-000000000000, thread=AUTO, type=DUAL) */
    public interface ITheServerComObject extends IUnknown
    {
    /** @com.method(vtoffset=4, dispid=1610743808, type=METHOD, name="HelloWorld")
    @com.parameters() */
    public void HelloWorld();

    /** @com.method(vtoffset=5, dispid=1610743809, type=METHOD, name="AcceptNewValue")
    @com.parameters([in,type=I4] lNewValue, [type=I4] return) */
    public int AcceptNewValue(int lNewValue);

    public static final com.ms.com._Guid iid = new com.ms.com._Guid((int)0x198c064d, (short)0xb949, (short)0x11d2,
    (byte)0x89, (byte)0x2, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0);
    }


    Some of the Highlights:

  • ITheServerComObject extends IUnkown. All COM interfaces extend IUnknown. We don't have to worry about IUnknown, it has been taken care of by Microsoft.

  • The @com.interface contains the clsid of the interface, we can verify this by the checking in the idl file for TheServer. The thread attribute set to AUTO means anything sent between threads in the VM will be marshalled. This is the safest implementation, the VM handles the Threading

  • In the vtable for this interface, we have 5 methods. Three(QueryInterface, AddRef and Release) are at vtoffset's 1, 2 and 3 respectively and come from IUnknown. At positions 4 and 5 are the two methods from this specific interface HelloWorld and AcceptNewValue

  • AcceptNewValue will appear again in the IMySink. This is the method TheServer calls in the Sink, through the Connection Point, to send the new value to the Java Client.

  • AcceptNewValue also has some parameters, these are specified in the @com.parameters. Both the input value and return type are int's

  • Each function has a separate dispid. This is a unique identifer for the specific method.

  • Finnally we have the GUID represented as a Java object of class com.ms.com._Guid.


  • IMySinkID.java:

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

    package theserver;

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

    // Dual interface IMySinkID
    /** @com.interface(iid=81323871-BCBA-11D2-8902-000000000000, thread=AUTO, type=DUAL) */
    public interface IMySinkID extends IUnknown
    {
    /** @com.method(vtoffset=4, dispid=1610743808, type=METHOD, name="RecieveNewData")
    @com.parameters([in,type=I4] lNewValue) */
    public void RecieveNewData(int lNewValue);

    public static final com.ms.com._Guid iid = new com.ms.com._Guid((int)0x81323871, (short)0xbcba, (short)0x11d2,
    (byte)0x89, (byte)0x2, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0);
    }


    Some of the Highlights:

  • IMySinkID extends IUnknown, just like all COM interfaces.

  • This is a DUAL interface. This means that the interface can be accessed through it's vtable or IDispatch. All interfaces in both examples are DUAL, and we are accessing the methods through the vtable, not IDispatch.

  • We have @interface directive. The clsid for the class, Thread is set to Auto so the VM will handle all Threading and Marshalling, Type is set to DUAL, which is described above.

  • The sink class has only one method, RecieveNewData, which is called by the COM Server, and the parameter is the new int value.

  • The RecieveNewData method is at the vtoffset 4 position, once again, the first three positions are the methods from IUnknown.

  • Finnally we have the GUID represented as a Java object of class com.ms.com._Guid.


  • TheServerComObject.java:

    Let's take a look at the Java version of the coclass TheServerComObject created by JActiveX for the ITheServerComObject interface

    package theserver;

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

    /** @com.class(classid=198C064E-B949-11D2-8902-000000000000,DynamicCasts) */
    public class TheServerComObject implements IUnknown,com.ms.com.NoAutoScripting,theserver.ITheServerComObject
    {
    /** @com.method()
    @hidden */
    public native void HelloWorld();

    /** @com.method()
    @hidden */
    public native int AcceptNewValue(int lNewValue);

    public static final com.ms.com._Guid clsid = new com.ms.com._Guid((int)0x198c064e, (short)0xb949, (short)0x11d2,
    (byte)0x89, (byte)0x2, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0);
    }


    Being a COM coclass, TheServerComObject implements the IUnknown interface and the ITheServerComObject 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 methods, this time their natives since it they are not defined in any Java classes. This tells the VM it has to make calls into the server object. It is only here to satisfy Java syntax requirements. They are hidden because they are not defined here, but in the COM object.

    Finnally, we define a GUID object for the iid.



    TheControlMultiThread.java:

    This is the interesting part! We are going to look at the highlights of the client program that uses this COM object: TheControlMultiThread.java
    The client program creates a Sink class that implements the IMySinkID interface. This program has multiple threads of execution. Creation and manipulation of the reference to the COM object happens on a separate thread from the GUI creation and manipulation. Let's look at the Declared Variables:

    public class TheControlMultiThread{
    int mainThreadID = 0; (1)
    public int objectThreadID = 0; (2)
    Frame theWindow; (3)
    Label nameLabel; (4)
    Panel thePanel; (5)
    WindowHandler winEvent; (6)
    ComObjectThread comThread; (7)


    Some of the Highlights:

  • Variable 1 and 2 - We need the ID's of both threads, becuase we need to manage and kill both of them at the right times

  • Variables 3, 4, 5 - The AWT components that make up the client. Essentially we have a frame with one Panel. On that panel will be a text label, and the background color of the panel will change in reponse to values sent by TheServer through the connection point to the Sink object. A screenshot is here.

  • Variable 6 - This is the listener for the Frame. When the user wants to close the Frame, the event is processed in this class. It destroys the frame and sends messages to both threads telling them to terminate.

  • Variable 7 - The ComObjectThread class extends the Thread class. This is the secondary thread responsible for getting the reference to the COM server, setting up the connection point, then termination everything when the user closes the client.


  • TheControlMultiThread.java:

    Let's look at the constructor for TheControlMultiThread:

    public TheControlMultiThread(){
    //This thread will also have it's own message loop
    com.ms.com.ComLib.declareMessagePumpThread();(1)
    mainThreadID = com.ms.win32.Kernel32.GetCurrentThreadId();(2)
    comThread = new ComObjectThread(this);(3)
    comThread.start(); (4)
    //we'll initialize the object and start up the connection pt
    //We should find some way to block the main thead until the com connections are set, but this
    //is a simple project and I am not going to worry about it. Since we have the id's of both threads
    //we could define msg that the object thread sends to the main thread saying it's got the
    connections
    //setup
    startGUI();(5)
    com.ms.win32.MSG mainThreadmsg = new com.ms.win32.MSG();(6)
    while (com.ms.win32.User32.GetMessage(mainThreadmsg, 0, 0, 0)) { com.ms.win32.User32.TranslateMessage(mainThreadmsg); if (mainThreadmsg.message==com.ms.win32.win.WM_QUIT) break; com.ms.win32.User32.DispatchMessage(mainThreadmsg); } System.exit(0);(7)
    }


    What's going on here?

  • Line 1 - We declare this as a Win32 message thread by making a native Win32 API(declareMessagePumpThread) call using it's java wrapper. This means this thread can process Win32 messages, and the VM will marshal anything between this thread and other threads.

  • Line 2 - We make another native Win32 API(GetCurrentThreadId()) call using it's java wrapper. We need the threadID so we can send Win32 messages to this thread, in particular, the WM_QUIT message.

  • Line 3 and 4 - We create a new thread object for holding our reference to the COM object. We pass it a reference to the control. Then in Line 4, we call the start() method, which calls the run() method. The thread class is explained below.

  • Line 5 - We tell the main thread to set up the GUI. This involves creating the Frame, adding the panel to it, with the background color set to green and some text inside, identifying the Java App.

  • Line 6 - Since this is a message pump thread, the VM will send it all Win32 messages, we need a loop to process them. This loop could be moved to a run() method, but I just left it here for simplicity. The message loop spins and the program runs until it recieves a WM_QUIT message. It breaks the loop and exits on Line 7.


  • TheControlMultiThread.java:

    The next place to go is the ComObjectThread class so we can see how the thread is setup. We will look at the constructor and the run() method:

    class ComObjectThread extends Thread{
    //Data this thread manipulates
    TheControlMultiThread theControl; (1)
    ITheServerComObject theServer; (2)
    TheSink sink; (3)
    com.ms.com.ConnectionPointCookie theConPt; (4)

    public ComObjectThread(TheControlMultiThread theControl){
    super();
    this.theControl = theControl;(5)
    //We need to see the main program's public data member for thread ID
    }

    public void run(){
    //has it's own message loop, manages the object and the connection pt
    try{
    com.ms.com.ComLib.declareMessagePumpThread();(6)
    //This is inside the object thread so we set the objectThreadID
    theControl.objectThreadID = com.ms.win32.Kernel32.GetCurrentThreadId();(7)
    theServer = (ITheServerComObject) new TheServerComObject();(8)
    //Give the server some time to come up
    sleep(1000);
    //setup the connection pt
    sink = new TheSink();(9)
    sink.SetParent(theControl);
    theConPt = new com.ms.com.ConnectionPointCookie(theServer, sink, Class.forName("theserver.IMySinkID"));(10)
    com.ms.win32.MSG objectThreadmsg = new com.ms.win32.MSG();(11)
    while (com.ms.win32.User32.GetMessage(objectThreadmsg, 0, 0, 0)) {
    com.ms.win32.User32.TranslateMessage(objectThreadmsg);
    if (objectThreadmsg.message==com.ms.win32.win.WM_QUIT)
    break;
    com.ms.win32.User32.DispatchMessage(objectThreadmsg);
    }
    theConPt.disconnect();(12)
    com.ms.com.ComLib.release(theServer);(12)
    Thread.sleep(1000);
    System.exit(0);
    }
    catch(ClassNotFoundException ex){(14)
    System.out.println("Could not load the class for the Sink Interface");
    com.ms.com.ComLib.release(theServer);
    System.exit(0);
    }
    catch(Exception ii){}
    }
    }


    How does this class work?

    Essentially, as follows! When the thread object is constructed, we give it the pointer to object that spawned it. When run() is call, the thread first declares itself as a message loop(for the same reasons the constructor of TheControlMultiThread did). It stores it's thread ID in the TheControlMultiThread Object that spawned it. Next we create our instance of the COM object, the virtual machine takes care of calling CoCreateInstance for us. We cast it to an ITheServerComObject. This because we can not work with the direct coclass, but make all our calls through the interface. Then we create an instance of TheSink object. We pass both sink object and server interface object to the ConnectionPointCookie constructor. This sets up the connection point based on the IMySinkID interface. This is also the reason for the try/catch, if we can't find the Class file for theserver.IMySinkID, a ClassNotFoundException is thrown, we catch this, release the reference on the COM interface and terminate the thread. Once the connection is established, we setup the message loop. When the WM_QUIT message is recieved, we break the connection with the server, release the reference on the COM interface and exit the thread.

  • Line 1 - This thread needs a reference to the Control, because it needs to pass it to the Sink object(line 9).

  • Line 2 - The thread object will hold a reference to the COM object interface.

  • Line 3&4 - It will also hold a reference to the Sink and to the Java object representing the Connection point with TheServer.

  • Line 5 - In the constructor, we assign the refernce to the control to our class variable.

  • Line 6&7 - We declare this a message recieving thread and set the public int objectThreadID in our main control to this thread's id. These are both Win32 API calls through their Java wrappers.

  • Line 8 - We create an instance of the COM coclass for TheServerComObject. We must cast it to the interface ITheServerComObject. We can not make calls directly on the COM object class, but must do it though the interface.

  • Line 9 - We need a sink object for the connection point. TheSink object class must implement the IMySinkID interface and is explained below.

  • Line 10 - We initialize with the connection point object, we pass it the reference to the sink object and the reference to the COM server interface object. Finnally, we must also tell it the class of the interface. If the VM can not find this class file, it throws an exception.

  • Line 11 - We setup the message loop for this thread.

  • Line 12 - When we reach this point, the WM_QUIT message was recieved. We now must break the connection with the server. This allows the server to clean up it's resources that were involved in the connection.

  • Line 13 - We release the reference on the COM interface and terminate execution.

  • Line 14 - Catch the ClassNotFoundException if we can't find the class for IMySinkID, release the reference to the COM interface and terminate.


  • TheControlMultiThread.java:

    Let's examine the class, TheSink. It is necessary to have an implementation of the IMySinkID interface to use connection points. The object that implements the interface is involved in the connection with the server. Let's look at the class:

    class TheSink implements IMySinkID{
    //Private Objects - Here we hold a reference to the control
    TheControlMultiThread parentControl;

    //Methods - No constructor, go with the default
    public void SetParent(TheControlMultiThread parentControl){
    this.parentControl = parentControl;
    }

    public void RecieveNewData(int lNewValue){
    parentControl.ChangeColor(lNewValue);
    }
    }


    How does this class work?

    The IMySinkID interface requires one method be implemented, RecieveNewData(). This is the function that the COM server will call when it has an update for the Java Program. The RecieveNewData() function calls a function in the control that will change the background color depending on the value recieved. The MySink object holds a reference to the main program, a TheControlMultiThread object reference. Note this reference does not get passed in the constructor, but a separate public function, SetParent, initializes the reference and needs to be called immediatly after the object is initialized.



    TheControlMultiThread.java:

    We need to look at one more class, the window event handler class, WindowHandler. This object listens for user actions on the window frame. Specifically, we have a handler for the event where the user tries to close the window. This method, windowClosing, first disposes of the window. Then it sends a WM_QUIT message to the object control thread. Once that thread has terminated, it sends a WM_QUIT message to the main control thread, and the program goes away. This is why the class TheControlMultiThread holds id's for both the main thread and the com object thread. Now we can send messages to both threads when termination is required.

    class WindowHandler extends WindowAdapter{
    Frame theWindow;
    int mainThreadID;
    int objectThreadID;

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

    public void windowClosing(WindowEvent evt){
    theWindow.dispose();
    //kill the worker thread with the objects first
    com.ms.win32.User32.PostThreadMessage(objectThreadID, com.ms.win32.win.WM_QUIT, 0, 0);
    try{
    Thread.sleep(1000);
    } catch(Exception ii) {}
    //now kill the main thread
    com.ms.win32.User32.PostThreadMessage(mainThreadID, com.ms.win32.win.WM_QUIT, 0, 0);
    }
    }