Example 1 - Simple COM HelloWorld Server and Java Driver - Kishore J Doshi
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 LinksCreating this server is simple and I will not get into the details here since that is not the purpose, but here are the steps:
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:
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.
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.
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)
}
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.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"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.