Hi There,
I'm very new to all things COM and comfyj, so please bear with me.
I've read the documentation and posts on this forum, but I think I am lacking some basic knowledge which I hope you will be able to kindly help me with.
For example, I have two classes in Java: Manager.class and Player.class defined simply as:
public class Player {
protected String name;
protected int age;
protected boolean injured;
public Player( String name, int age, boolean injured ) {
this.name = name;
this.age = age;
this.injured = injured;
}
public String getName() { return name; }
public void setName(String n) { this.name = n; }
public boolean isInjured() { return injured; }
public void setInjured(boolean b) { this.injured = b; }
public int getAge() { return age; }
public void setAge(int age) { this.age - age; }
}
public class Manager {
protected Vector<Player> players = new Vector<Player>();
protected String managerName = "John";
protected boolean active = true;
protected int age = 30;
public Manager() {
players.add( new Player("Player 1", 30, false) );
players.add( new Player("Player 2", 30, true) );
}
public Vector<Player> getPlayers() { return players; }
public String getManagerName() { return managerName; }
public void setManagerName(String n) { this.managerName = n; }
public boolean isActive() { return active; }
public void setActive(boolean b) { this.active = b; }
public int getAge() { return age; }
public void setAge(int age) { this.age - age; }
}
What is the correct way to expose both those classes so that they could be accessed through the com interface?
I know that I can return Strings as com.jniwrapper.win32.automation.types.BStr, how about Vector,int, boolean, etc...?
If I define a ManagerComServer, is it possible to have access to my Player class through the COM interface also?
I would like to mimic something like:
Manager manager = new Manager();
String mName = manager.getManagerName();
Player player = manager.getPlayers().get(0);
String pName = player.getName();
int pAge = player.getAge();
player.setInjured(false);
boolean pInjured = player.isInjured();
Thank you for any help or pointers to help,
Best regards,
Fabricio.
I've gotten this far:
Generate a new CLSID for my COM server (from www.guidgen.com):
{D1F3EAED-780E-46cd-B504-3919FFEAE887}
The IDispatch Interface.
package comfyj.test;
import com.jniwrapper.Int;
import com.jniwrapper.win32.automation.IDispatch;
import com.jniwrapper.win32.automation.types.BStr;
import com.jniwrapper.win32.automation.types.VariantBool;
/**
* Comfyj Test class (IDispatch Interface).
* @author Fabricio Sanchez
*/
public interface IManager extends IDispatch {
// What about the constructor for Manager? Is it defined here?
// Do I need an extra method to contruct Manager?
// What is the equivalent for the method below?
//public Vector<Player> getPlayers();
public BStr getManagerName();
public void setManagerName(BStr n);
public VariantBool isActive();
public void setActive(VariantBool b);
public Int getAge();
public void setAge(Int age);
}
The Manager COM Server
package comfyj.test;
import com.jniwrapper.Int;
import com.jniwrapper.win32.automation.types.BStr;
import com.jniwrapper.win32.automation.types.VariantBool;
import com.jniwrapper.win32.com.ComException;
import com.jniwrapper.win32.com.DispatchComServer;
import com.jniwrapper.win32.com.IPersist;
import com.jniwrapper.win32.com.server.CoClassMetaInfo;
import com.jniwrapper.win32.com.types.CLSID;
/**
* Comfyj Test class (Java COM Server).
* @author Fabricio Sanchez
*/
public class ManagerComServer extends DispatchComServer implements IPersist, IManager {
public static final CLSID COM_SERVER_CLSID = new CLSID("{D1F3EAED-780E-46cd-B504-3919FFEAE887}");
public static final String PROG_ID = "comfyj.test.1";
public static final String VERSION_INDEPENDENT_PROG_ID = "comfyj.test";
public static final String COM_SERVER_DESCRIPTION = "Java COM Server Test.";
private BStr progID = new BStr(PROG_ID);
private BStr versionIndependentProgId = new BStr(VERSION_INDEPENDENT_PROG_ID);
private BStr comServerDescription = new BStr(COM_SERVER_DESCRIPTION);
private Manager manager;
public ManagerComServer(CoClassMetaInfo classImpl) {
super(classImpl);
}
/**
* This method retrieves the class identifier (CLSID) of an object.
*
* @param pClassID the class identifier (CLSID) of an object
* @throws ComException
*/
public void getClassID(CLSID pClassID) throws ComException {
pClassID.setData1(COM_SERVER_CLSID.getData1());
pClassID.setData2(COM_SERVER_CLSID.getData2());
pClassID.setData3(COM_SERVER_CLSID.getData3());
pClassID.setData4(COM_SERVER_CLSID.getData4());
}
public BStr getProgId() {
return progID;
}
public BStr getVersionIndependentProgId() {
return versionIndependentProgId;
}
public BStr getComServerDescription() {
return comServerDescription;
}
// What about the constructor for Manager? Is it defined here?
// Do I need an extra method to contruct Manager?
// What is the equivalent for the method below? SafeArray?
//public Vector<Player> getPlayers();
public BStr getManagerName() {
if (manager == null) manager = new Manager();
return new BStr(manager.getManagerName());
}
public void setManagerName(BStr n) {
if (manager == null) manager = new Manager();
manager.setManagerName(n.getValue());
}
public VariantBool isActive() {
if (manager == null) manager = new Manager();
return new VariantBool(manager.isActive());
}
public void setActive(VariantBool b) {
if (manager == null) manager = new Manager();
manager.setActive(b.getBooleanValue());
}
public Int getAge() {
if (manager == null) manager = new Manager();
return new Int(manager.getAge());
}
public void setAge(Int age) {
if (manager == null) manager = new Manager();
manager.setAge((int)age.getValue());
}
}
The Manager COM Client
package comfyj.test;
import com.jniwrapper.win32.automation.Automation;
import com.jniwrapper.win32.automation.IDispatch;
import com.jniwrapper.win32.automation.OleMessageLoop;
import com.jniwrapper.win32.automation.impl.IDispatchImpl;
import com.jniwrapper.win32.automation.types.BStr;
import com.jniwrapper.win32.automation.types.Variant;
import com.jniwrapper.win32.com.ComException;
import com.jniwrapper.win32.com.impl.IPersistImpl;
import com.jniwrapper.win32.com.types.CLSID;
import com.jniwrapper.win32.com.types.ClsCtx;
/**
* Comfyj Test class (Java COM Client).
* @author Fabricio Sanchez
*/
public class ManagerComClient {
private static final String GET_PROG_ID_METHOD_NAME = "getProgId";
private static final String GET_VERSION_INDEPENDENT_PROG_ID_METHOD_NAME = "getVersionIndependentProgId";
private static final String GET_COM_SERVER_DESCRIPTION_METHOD_NAME = "getComServerDescription";
private static final String GET_MANAGER_NAME_METHOD_NAME = "getManagerName";
private static final String SET_MANAGER_NAME_METHOD_NAME = "setManagerName";
private static final String IS_ACTIVE_METHOD_NAME = "isActive";
private static final String SET_ACTIVE_METHOD_NAME = "setActive";
private static final String GET_AGE_METHOD_NAME = "getAge";
private static final String SET_AGE_METHOD_NAME = "setAge";
public static void main(String[] args) throws Exception {
Runnable runnable = new Runnable() {
public void run() {
try {
// CLSID from the VERSION_INDEPENDENT_PROG_ID
IDispatch dispatch = new IDispatchImpl(CLSID.createFromProgID("comfyj.test"), ClsCtx.LOCAL_SERVER);
IPersistImpl p = new IPersistImpl(dispatch);
CLSID clsid = new CLSID();
p.getClassID(clsid);
System.out.println("ClassID = " + clsid);
Variant result = new Variant();
Automation automation = new Automation(dispatch);
result = automation.invoke(GET_PROG_ID_METHOD_NAME);
System.out.println("PROG_ID = " + result.getValue());
result = automation.invoke(GET_VERSION_INDEPENDENT_PROG_ID_METHOD_NAME);
System.out.println("VERSION_INDEPENDENT_PROG_ID = " + result.getValue());
result = automation.invoke(GET_COM_SERVER_DESCRIPTION_METHOD_NAME);
System.out.println("COM_SERVER_DESCRIPTION = " + result.getValue());
// Calling Manager Methods
Variant[] mName = new Variant[] { new Variant(new BStr("Bob")) };
automation.invoke(SET_MANAGER_NAME_METHOD_NAME, mName);
result = automation.invoke(GET_MANAGER_NAME_METHOD_NAME);
System.out.println(GET_MANAGER_NAME_METHOD_NAME + " = " + result.getBstrVal().getValue());
Variant[] mActive = new Variant[] { new Variant(true) };
automation.invoke(SET_ACTIVE_METHOD_NAME, mActive);
result = automation.invoke(IS_ACTIVE_METHOD_NAME);
System.out.println(IS_ACTIVE_METHOD_NAME + " = " + result.getBoolVal().getBooleanValue());
Variant[] mAge = new Variant[] { new Variant(45) };
automation.invoke(SET_AGE_METHOD_NAME, mAge);
result = automation.invoke(GET_AGE_METHOD_NAME);
System.out.println(GET_AGE_METHOD_NAME + " = " + (int)result.getIntVal().getValue());
// #TODO Get a List of players and apply methods to them
}
catch (ComException e)
{
e.printStackTrace();
}
}
};
OleMessageLoop.invokeAndWait(runnable);
OleMessageLoop.stop();
}
}
I then compiled the whole lot into a jar file and used ServerManager to register my comfyj.test.ManagerComServer.
I get the following error when I run my ManagerComClient class:
ClassID = {D1F3EAED-780E-46CD-B504-3919FFEAE887}
Exception in thread "main" java.lang.reflect.InvocationTargetException
at com.jniwrapper.win32.MessageLoopThread.doInvokeAndWait(MessageLoopThread.java:232)
at com.jniwrapper.win32.automation.OleMessageLoop.invokeAndWait(SourceFile:179)
at comfyj.test.ManagerComClient.main(ManagerComClient.java:84)
Caused by: java.lang.NullPointerException
at comfyj.test.ManagerComClient$1.run(ManagerComClient.java:49)
at com.jniwrapper.win32.MessageLoopThread$ThreadSynchronizedAction.run(MessageLoopThread.java:569)
at com.jniwrapper.win32.MessageLoopThread$LoopThread.run(MessageLoopThread.java:511)
Java Result: 1
Line 49 is:
System.out.println("PROG_ID = " + result.getValue());
Can anybody help, please?
Other questions...
Is the IManager interface I created required?
Can I create a SafeArray with my Player class on it? Is this the best alternative to a java Vector<Player>?
Thank you,
Fabricio
Just figured out the error was getting before.
I needed to have the following methods defined on my IManager interface:
public BStr getProgId();
public BStr getVersionIndependentProgId();
public BStr getComServerDescription();
They were already defined on my ManagerComServer class, which answers another of my questions:
Is the IManager (IDispatch extension) interface required?
YES. If the methods are just defined on the ManagerComServer class and do not exist on the IManager interface, they can not be called by the client.
I still need help with my Player class and returning a SafeArray or someway of returning and accessing a Vector<Player>.
Fabricio
Hi Fabricio,
I am sorry for the delay in replaying to you.
Your initial steps are correct. Yes, IManager COM interface is required and it should be derived from IDispatch COM interface. Also, no additional methods (to construct a Manager object) in IManager interface are required:
public interface IManager extends IDispatch {
public BStr getManagerName();
public void setManagerName(BStr n);
public VariantBool isActive();
public void setActive(VariantBool b);
public Int getAge();
public void setAge(Int age);
}
In order to implement the public Vector<Player> getPlayers() method in COM server you can use standard IEnumUnknown or IEnumVariant COM interfaces as the return type or via SafeArray object, for example:
public interface IManager extends IDispatch {
public IEnumUnknown getPlayers();
}
In this case method should return the IEnumUnknown COM interface to your COM object (Java COM implementation of IEnumUnknown interface) that contains the Player objects, represented by IPlayer COM interface.
And of course, the most simplest way is to return that collection of objects via SafeArray:
public interface IManager extends IDispatch {
public SafeArray getPlayers();
}
You have also correctly implemented the ManagerComServer COM server. Though you could skip the implementation of IPersist COM interface, which was used in the example only for reference, to demonstrate that Java COM server actually can implement any number of standard COM interfaces as well as custom ones:
public class ManagerComServer extends DispatchComServer implements IManager {
public static final CLSID COM_SERVER_CLSID = new CLSID("{D1F3EAED-780E-46cd-B504-3919FFEAE887}");
public static final String PROG_ID = "comfyj.test.1";
public static final String VERSION_INDEPENDENT_PROG_ID = "comfyj.test";
public static final String COM_SERVER_DESCRIPTION = "Java COM Server Test.";
....
}
This server can be registered in the system using the ServerManager application or via its API. All these steps are described in ComfyJ programmers Guide: http://www.teamdev.com/downloads/comfyj/docs/ComfyJ-PGuide.html#AEN607 After registering this server any COM application can create and use it.
There are several requirements for creating such Java COM server application correctly:
1) It should be built using a certain Java language level in the mind. This means that if you build it using JDK 5 or 6 then it this application cannot be started by earlier JDKs. That's possible for example, when you do not specify the certain JDK while registering Java COM server and JDK 1.4 is set as default in the system;
2) All required license files should be located in the \META-INF sub folder of the JAR file;
3) Native JNIWrapper library (jniwrap.dll and/or jniwrap64.dll) should be located in the root of this file;
4) All depended libraries should be specified in the manifest of this JAR file;
Therefore this application JAR file should be only JAR which you register by ServerManager. Such approach helps to minimize the length of command line which starts Java COM server, because there is such restriction in COM.
And that NullPointerException exception could indicate that server could not be created. In any case you should use the following approach in order to verify that:
IDispatch server = new IDispatchImpl(CLSID.createFromProgID("comfyj.test"), ClsCtx.LOCAL_SERVER);
if (server.isNull()) {
// server object was not created successfully
} else {
// otherwise you can continue working with it
}
Also, you have mentioned that (in your further comments) that all that methods (getProgId(), getVersionIndependentProgId() and getComServerDescription()) were not included to the IManager interface. They definitely should be included to the dispatch interface if you plane to invoke them in a client application. But that's quite strange that code could even get to that point. That's because there should be exception (DISP_E_UNKNOWNNAME) thrown if you try to call a method which does not exist in a server.
One additional hint for debugging Java COM servers: when registering a server please use java.exe, but not javaw.exe. When you specify java.exe the Java console should be displayed and thus you can see any exception that could occur in the server application.
You can try the sample Java COM server application which is available on our site: ftp://ftp.teamdev.com/demo/JavaCOMServerSample.zip. To register this example you just need to unpack the archive to some folder (e.g. C:\Server\) and register the server.jar using the ServerManager and specify Java 6 executable. I have made this example to demonstrate how to create simple ActiveX components using ComfyJ. And after registering this example application you can even embed the SimpleSwingActiveX component into various OLE containers. Also, note that this example contains the latest builds of ComfyJ and JNIWrapper libraries and there are numerous fixes in it since version 2.4.
Please let me know if you have any further questions.
Regards,
-Serge
Thank you Serge, that helped a lot.
What should my IPlayer interface extend? IDispatch? So that I can place it in a SafeArray for later usage.
Once I have my SafeArray of IPlayer Objects on my client class, can I access the methods on it directly or do I have to call invoke()?
My current IPlayer interface:
public interface IPlayer extends IDispatch {
public BStr getName();
public void setName(BStr n);
public VariantBool isInjured();
public void setInjured(VariantBool b);
public Int getAge();
public void setAge(Int age);
}
These are the modifications to the code, which is failing with another NullPointerException at the moment:
Added 2 new methods to ManagerComServer (which doesn't implement IPersist anymore, thank you):
public class ManagerComServer extends DispatchComServer implements IManager {
....
public Int getNumberOfPlayers() {
if (manager == null) manager = new Manager();
Vector<Player> players = manager.getPlayers();
if (players == null) return new Int(0);
else return new Int(players.size());
}
public SafeArray getPlayers() {
if (manager == null) manager = new Manager();
Vector<Player> players = manager.getPlayers();
SafeArrayBound size = new SafeArrayBound(players.size(),0);
SafeArray array = new SafeArray(size,IPlayer.class);
for (int i = 0; i < players.size(); i++) {
array.set(i, (Parameter)(IPlayer)players.get(i));
}
array.setAutoDelete(false);
return array;
}
...
}
And added the 2 supporting methods to the IManager interface:
public interface IPlayer extends IDispatch {
...
public Int getNumberOfPlayers();
public SafeArray getPlayers();
....
}
My ManagerComClient is trying to access these methods as follows:
public class ManagerComClient {
....
private static final String GET_NUMBER_OF_METHOD_NAME = "getNumberOfPlayers";
private static final String GET_PLAYERS_METHOD_NAME = "getPlayers";
....
public static void main(String[] args) throws Exception {
....
result = automation.invoke(GET_NUMBER_OF_METHOD_NAME);
System.out.println(GET_NUMBER_OF_METHOD_NAME + ":" + (int)result.getIntVal().getValue() );
// Works ok, returns correct Int (value of 2).
result = automation.invoke(GET_PLAYERS_METHOD_NAME);
SafeArray array = result.getSafeArray();
System.out.println(GET_PLAYERS_METHOD_NAME + ":");
for (int i = 0; i < array.getCount(); i++) {
Object o = array.get(i);
System.out.println("Element # " + i + " = " + o);
}
// The above doesn't work, it returns a null pointer exception.
}
}
I get the following on my console:
PROG_ID = comfyj.test.1
VERSION_INDEPENDENT_PROG_ID = comfyj.test
COM_SERVER_DESCRIPTION = Java COM Server Test.
getManagerName = Bob
isActive = true
getAge = 45
getNumberOfPlayers:2
Exception in thread "main" java.lang.reflect.InvocationTargetException
at com.jniwrapper.win32.MessageLoopThread.doInvokeAndWait(MessageLoopThread.java:232)
at com.jniwrapper.win32.automation.OleMessageLoop.invokeAndWait(SourceFile:179)
at comfyj.test.ManagerComClient.main(ManagerComClient.java:101)
Caused by: java.lang.NullPointerException
at comfyj.test.ManagerComClient$1.run(ManagerComClient.java:85)
at com.jniwrapper.win32.MessageLoopThread$ThreadSynchronizedAction.run(MessageLoopThread.java:569)
at com.jniwrapper.win32.MessageLoopThread$LoopThread.run(MessageLoopThread.java:511)
Java Result: 1
// where line 85:
SafeArray array = result.getSafeArray();
Any further help is appreciated,
Regards,
Fabricio
Hi Fabricio,
Yes, IPlayer must extend IDispatch interface, otherwise its methods will not be registered in the Java COM server. Also, supposing that getPlayers() method returns SafeArray object, the getNumberOfPlayers() method is not necessary, because SafeArray object (descriptor of an array) already contains the number of elements.
Without the sample it's difficult to say why that NullPointer exception occurs. I have tried to recreate such issue in my environment, but the method which returns a SafeArray object worked on a client without any problem. Can you please check which versions of ComfyJ and JNIWrapper are used by your server application? Please make sure that this a latest ones, which are available in a sample application that I have provided before. Also, you can download it as separate archive from the following link: ftp://ftp.teamdev.com/updates/comfyj-2.4.902.zip
But if the problem persists please send me a sample application that reproduces the problem. I will investigate it and let you know the solution.
-Serge
Hi Serge,
I must be missing something basic... I have made IPlayer extend IDispatch, but it still gives me an error, even after cpying the latest version that you sent me. Do I have to register it somewhere?
Please find attached the code for the application (manager.jar), please note that the 3 required jar files (winpack,jniwrapper,comfyj) must be in a lib directory at the same level as the manager.jar itself.
The getNumberOfPlayers() method is more of a test to ensure that part is working. Once I get the SafeArray working I'll start using that instead.
This is the error I now get:
Checking JNIWrapper license
...
JNIWrapper license valid.
Checking ComfyJ license
...
ComfyJ license valid.
PROG_ID = comfyj.test.1
VERSION_INDEPENDENT_PROG_ID = comfyj.test
COM_SERVER_DESCRIPTION = Java COM Server Test.
getManagerName = Bob
isActive = true
getAge = 45
getNumberOfPlayers:2
Exception in thread "main" java.lang.reflect.InvocationTargetException
at com.jniwrapper.win32.MessageLoopThread.doInvokeAndWait(MessageLoopThread.java:232)
at com.jniwrapper.win32.automation.OleMessageLoop.invokeAndWait(SourceFile:179)
at comfyj.test.ManagerComClient.main(ManagerComClient.java:101)
Caused by: java.lang.NullPointerException
at comfyj.test.ManagerComClient$1.run(ManagerComClient.java:85)
at com.jniwrapper.win32.MessageLoopThread$ThreadSynchronizedAction.run(MessageLoopThread.java:569)
at com.jniwrapper.win32.MessageLoopThread$LoopThread.run(MessageLoopThread.java:511)
Java Result: 1
BUILD SUCCESSFUL (total time: 3 seconds)
Thank you,
Regards,
Fabricio
Hi Fabricio,
I have found the cause of that problem. It turned out that getPlayers() of ManagerComServer class was incorrectly implemented. I have made the required changes and that started working correctly. Also, I have simplified some classes a little bit.
Please try this update and let me know the results.
-Serge
Hi Serge,
Thank you very much for your help.
I was very far from getting to a correct answer.
I don't understand all the changes yet...
So far, this is what I understand:
Java Objects: Manager and Player. Such that a Manager instance has many Player instances.
1. Create the IDispatch interface for the classes above: interface IManager and interface IPlayer (they both extend IDispatch) and define any methods from Manager and Player that you want exposed to access by the COM clients. You must change the parameter and return types for the interfaces so that they use their comfyj/jniwrapper equivalents.
(e.g. String becomes BStr, boolean becomes VariantBoolean, int becomes Int, double becomes DoubleFloat, Collections become SafeArray, ...)
// Example
public class Player {
protected String name;
public String getName() { return name; }
public void setName(String n) { this.name = n; }
}
public interface IPlayer extends IDispatch {
public BStr getName();
public void setName(BStr n);
}
2. Since Manager is the main Java class, (that is, the class that contains the main() method and the class we would initially run in Java) a ManagerComServer class will be created and registered, so that it too is the first point of call for COM clients. This class must implement all the methods described in the IManager interface.
public class ManagerComServer extends DispatchComServer implements IManager {
// You will need a unique CLSID, I got mine from http://www.guidgen.com, more info there.
public static final CLSID COM_SERVER_CLSID = new CLSID("{D1F3EAED-780E-46cd-B504-3919FFEAE887}");
public static final String PROG_ID = "comfyj.test.1";
public static final String VERSION_INDEPENDENT_PROG_ID = "comfyj.test";
public static final String COM_SERVER_DESCRIPTION = "Java COM Server Test.";
...
}
3. You must then register the ManagerComServer using ServerManager (Graphical Interface) or in code (Not sure how to do it in code yet...)
Please see the manager.jar file attached to the post above for the correct implementation of all these classes.
4. The Player class will not be accessed separately from the Manager class, so it will not be registered as a separate ComServer using ServerManager or otherwise. However we will need a PlayerServer class that creates a wrapper (PlayerImpl) that uses Automation to access the original Player instance.
Please look at the implementations of IPLayerServer and IPlayerImpl for the code to do this (on manager.jar attached to the post above).
5. The last stage is to create a Test client class to access the Objects. This is the ManagerComClient class in this case. Please refer to it also for more information.
Any comments that complete/improve on this post are appreciated.
Regards,
Fabricio
Hi Fabricio,
Yes, your understanding is quite correct.
I just would like to clarify why I have added these IPlayerServer and IPlayerImpl classes. First (IPlayerServer) is the COM wrapper class for the Player object which is an upper COM layer for the corresponding Java class. It's actually the similar to ManagerComServer class that implements IManager dispatch interface and encapsulates the working with Manager object. The second IPlayerImpl was added for the convenience. Actually I could even use the IDispatch interface instead, but then in order to invoke its methods I would need to create an Automation object. So, I hope that should explain a little bit the changes that I have made. But if you still have unanswered questions please let me know.
Also, I would like to mention that in one of the future versions of ComfyJ we are going to introduce new feature that will simplify the exposing of already created Java objects as COM objects. This means that you will not need to create any additional Java COM wrappers by your own because ComfyJ will do that for you. And I hope that should greatly simply the usage of API.
-Serge