Example 2 - DS 420 DCOM Project with a Java Control - Kishore J Doshi
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.
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:
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:
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:
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.
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:
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?
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.
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.
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);
}
}