JNA COM Feedback/Suggestions

219 views
Skip to first unread message

Ian Darby

unread,
Mar 10, 2013, 10:35:40 AM3/10/13
to jna-...@googlegroups.com

Hi Tobias,

I have now ported around 80%-90% of the MS Outlook object model (over 100 classes) to your COM framework. Having also previously completed a similar exercise using the eclipse swt COM framework and having dabled with several other similar frameworks (such as Jacob) I feel able to speak with some authority on ease of use and what works/what doesn't work.

Overall my impressions are very favourable, especially given that this is still an embryonic  piece of work. I especially like the fact that it is part of the JNA project. This absolutely feels like the right place for it.

Having used it quite a bit now I feel comfortable saying that the following three things would turn a really good first effort in to a solid and simple to use framework (I'll list them then explain each in detail):

1. Minor changes to the way the programmer interacts with the VARIANT class;

2. A few extra convenience methods in the COMObject base class;

3. A MockIDispatchObject class to implement the IDispatch interface for unit testing purposes.

1. Changes to the VARIANT class:
================================
When implementing a set of real world COM wrappers (such as for the MS Office object model) it is typically necessary to create and use many hundreds of instances of VARIANT for parameter passing to and from COM properties and methods. It would make life so much easier if:

* there were constructors accepting all the basic Java types (short, int, String, Date, IDispatch, etc). My work around has been to provide my own base class between COMObject and my actual wrappers and provide it with methods having prototypes like:

    VARIANT newVariant(short val);
    VARIANT newVariant(String val);
    VARIANT newVariant(java.util.Date val);

* there were methods on the class for extracting the same Java type values:

    var.shortValue();
    var.intValue();
    var.stringValue();
    var.dateValue();
    etc.
 
I found no work round for having to manually navigate the getValue() returned intermediate object and it was a real pain point. I know that platform portability is of prime concern to the JNA project but... as far as I am aware 'COM' is only pertinent to Windows so this should not be a problem.

2. COMObject convenience methods:
=================================
I estimate that well over 99% of the times that I wanted to interact with the underlying IDispatch methods/properties it was for the same IDispatch as stored within COMObject. That coupled with the need to create a return VARIANT object for every call makes directly using the oleMethod() calls overly verbose and complex.

As a work around I took a leaf out of the swt COM framework book and added simplified invoke() and invokeNoReply() methods to my base class that inherited from COMObject. The prototype for these methods being of the form:

    VARIANT invoke(String methodName);
    VARIANT invoke(String methodName, VARIANT[] args); 
    VARIANT invoke(String methodName, VARIANT arg1); 
    VARIANT invoke(String methodName, VARIANT arg1, VARIANT arg2);
    etc.

I did a similar thing for methods that returned no value:

    void invokeNoReply(String methodName);
    void invokeNoReply(String methodName, VARIANT[] args);
    void invokeNoReply(String methodName, VARIANT arg1);
    void invokeNoReply(String methodName, VARIANT arg1, VARIANT arg2);
    etc.

Finally I provided generic property getters and setters that took/returned Java types directly. For example:

    short getShortProperty(String propertyName);
    void setProperty(short value);
    int getIntProperty(String propertyName);
    void setProperty(int value);
    String getStringProperty(String propertyName);
    void setProperty(String text);
    java.util.Date getDateProperty(String propertyName);
    void setProperty(java.util.Date date);
    IDispatch getIDispatchProperty(String propertyName);
    void setProperty(IDispatch iDisp);
 
By providing these inherited methods (ideally at the COMObject level) the implementation code for the typical wrapper method drops to around 1 simple line of code. This is very important when there are typically many hundreds of these methods to implement. A typical method implementation then takes the simple and elegant form:

    public Date getCreationTime() {
 
        return getDateProperty("CreationTime");
    }
 
    public void resetForm() {
 
        invokeNoReply("ResetForm");
    }

3. MockIDispatchObject class
============================
This is not unique to your framework, though yours provides a nice simple mechanism for implementing unit tests that mock up the dependency on the underlying third party application. Let me explain.

Historically I have been a really bad boy when it comes to creating unit tests for desktop user applications (I normally work on server-side applications with no user interface). However, the sensible project policy for JNA of not accepting code without unit tests got me thinking. I can get away with depending on things like Outlook while it is on my PC, but that is unacceptable for a central (automated?) build system.

Initially I was thinking that I would need to mock all my application classes using something like 'mockrunner'. However, after a bit of thought I realised that due to the way that the JNA COM framework works, the problem was very generic and relatively easily solved.

With one exception all my COM wrappers are created by passing an IDispatch object in to the constructor (and the exception, initial creation of the application object, also relied on the same thing - it just obtained it in a different way). So all we need is a single generic MockIDispatchObject class which I suggest would have a public interface something like this:

    public class MockIDispatchObject implements IDispatch {
 
        // Called by unit test to setup the object to accept the method/property call.
        // Should be able to register multiple calls to allow testing of applications
        // based on the wrappers rather than just the wrappers.
        void registerMethodCall(String methodName, int methodType, VARIANT[] args, VARIANT returnValue);
        // May be also simplified variations with between zero and four arg parameters.
  
        // Check how many arguments were actually passed in the test
        int getActualArgumentCount();
  
        boolean assertAllArgumentsCorrect();
  
        // Check that the given argument number had the expected values in the test
        boolean assertActualArgumentValueCorrect(int argIndex);
  
        // If argument value assertion fails then retrieve the value actually passed in
        VARIANT getActualArgument(int argIndex);
  
        // Then the actual interface methods would be implemented to use the registered values
    }
 
I imagine that a typical unit test case would then look something like this:

    public class TestOutlook extends TestCase {
 
        private MockIDispatchObject iDisp;
        private SomeComWrapper comWrapper;
  
        private void setUp() {
     
            iDisp = new MockIDispatchObject();
            comWrapper = new SomeComWrapper(iDisp);
        }
  
        public void testSomeMethod() {
  
            VARIANT expectedReturn = new VARIANT(expectedReturnValue);
            VARIANT[] args = { new VARIANT(arg1Val), new VARIANT(arg2Val) };
            iDisp.registerMethodCall("SomeMethod", OleAuto.DISPATCH_METHOD, args, expectedReturn);
   
            int result = comWrapper.someMethod(arg1Val, arg2Val);
   
            // Normal assertion that arguments and result were as expected
            assertTrue("At least 1 actual argument was wrong", iDisp.assertAllArgumentsCorrect());
            assertEquals("Unexpected result", expectedReturnValue, result);
   
            // Further debugging if iDisp.assertAllArgumentsCorrect() was false
            // assertEquals(2, iDisp.getActualArgumentCount());
            // assertTrue("1st Argument value incorrect", iDisp.assertActualArgumentValueCorrect(0));
            // assertTrue("2nd Argument value incorrect", iDisp.assertActualArgumentValueCorrect(1));
        }
    }
 
Oh. I have not forgotten about donating my Outlook wrappers to your project. However, without tests it is not possible right now. So if I can figure out GitHub I will temporarily post it to my own site and then post the link here so people can get an advanced peek.

Regards,
Ian Darby

Timothy Wall

unread,
Mar 10, 2013, 12:49:14 PM3/10/13
to jna-...@googlegroups.com
Thanks for the feedback, Ian.
> --
> You received this message because you are subscribed to the Google Groups "Java Native Access" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to jna-users+...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Timothy Wall

unread,
Mar 10, 2013, 12:51:22 PM3/10/13
to jna-...@googlegroups.com
Because the whole COM package is potentially large and complex, it might make sense to package it separately from the platform.jar.

On Mar 10, 2013, at 10:35 AM, Ian Darby wrote:

Ian Darby

unread,
Mar 10, 2013, 4:02:44 PM3/10/13
to jna-...@googlegroups.com
Hi Tim,
 
Yes I agree that it would make more sense to put the office interface in to a seperate jar.
 
I've uploaded my initial Outlook source code to my own GitHub repository as a temporary measure. Those who want an early look can find it here:
 
 
Be warned that for the moment it is largely untested and that I have yet to add Javadoc comments. For the moment those wanting to experiment can use the standard MS Outlook object model documentation. the classes generally mirror those found in the MS model and just delegate method and property calls to MS Outlook.
 
There are just a couple of minor changes:
 
1.  Property names are prefixed with 'get/set/is' as per Java convention;
 
2.  All method names start with a lowercase character;
 
3.  Integer (Java short) enumerations are replaced with a type-safe class of the same/similar name.
 
Regards,
Ian Darby

Tobias Wolf

unread,
Mar 11, 2013, 12:17:00 PM3/11/13
to jna-...@googlegroups.com
Hi Ian,

thanks for the big response, it contains a lot of good comments which bring me further with the development to improve the COM support within JNA!
You made a large example for the topic around outlook and you thought about all aspects within that, but honestly I think to add that as it is to the samples area would break the limits of a "simple sample". Maybe it could make
sense to start a new section under the hood of JNA named MS Office support which covers the application side support on a higher level of abstraction as JNA does. But that needs to be discussed here and people needs to take in charge of this
job, that`s definitely to big for only one person.
Anyway I let you know when I`m finished with the work around the convenient methods for the COM support.

Ian Darby

unread,
Mar 11, 2013, 1:19:07 PM3/11/13
to jna-...@googlegroups.com
Hi Tobias,
 
both Tim and yourself make a good point about the size/appropriateness of including this in the samples. It does make more sense to have have seperate jar files created for full blown application wrapping (probably one per application - or application suite). It is my intention/need to also create similar style wrappers for the Excel and Word object models (PowerPoint and Access are not such a pressing need for me).
 
For the moment I will leave my code on my newly created GitHub site mentioned above. I'll keep it up to date as I progress and once I get my head round using Git from eclipse I'll sort out a more appropriate structure for the project. Updates may only happen to my site every few days as my development machine is prevented from using Git by company security policies so I have to shuffle the project between environments first.
 
Perhaps people could start a library of fully wrapped COM applications. For instance I will also be needing to do a similar thing for Attachmate Extra as well as the above mentioned Office applications.
 
I'm happy to pitch in on getting the mock IDispatch object created, but I'm afraid that I'm a unix man and don't have that deep a knowledge of Windows so you would have to be quite prescriptive about what I needed to do.
 
Regards,
Ian Darby

On Sunday, March 10, 2013 2:35:40 PM UTC, Ian Darby wrote:

Tobias Wolf

unread,
Mar 15, 2013, 6:46:26 AM3/15/13
to jna-...@googlegroups.com
Hi Ian,

I`ve just uploaded the source codes which covers the point 1+2. About 3 I`m currently not sure if I implements that, its a good idea but I`m spending my time better in a new feature for automatic code generation from COM interface / Typelib. I think such kind of feature would be helpful just for creating a java stub file from every COM object instead of writing everything by you own.

Timothy Wall

unread,
Mar 15, 2013, 7:05:25 AM3/15/13
to jna-...@googlegroups.com

On Mar 15, 2013, at 6:46 AM, Tobias Wolf wrote:

> Hi Ian,
>
> I`ve just uploaded the source codes which covers the point 1+2. About 3 I`m currently not sure if I implements that, its a good idea but I`m spending my time better in a new feature for automatic code generation from COM interface / Typelib. I think such kind of feature would be helpful just for creating a java stub file from every COM object instead of writing everything by you own.

I totally agree about the usefulness of such code generation from a typelib. I probably can't help much on the COM specifics, but I might be able to facilitate on any build, API, or invocation issues.

In addition, I'm open to making native-side changes if they make sense - I recall looking at some of the com4j stuff (that project has a typelib-based code generator written by the very prolific kohsuke) and seeing a few COM-related things performed native side that might be tricky with only the native APIs that JNA currently supports.

T.

Tobias Wolf

unread,
Mar 15, 2013, 7:59:02 AM3/15/13
to jna-...@googlegroups.com
Hi Timothy,

thanks for offering your help. I was just starting with the investigating how the code generating could be made. I thought it to make a code generator which take the COM interface information from ITypeInfo interface which I`ve already implemented. That ITypeInfo interface can be retrieved by a instance of COMObject or from the loadTypeLib method. I`m thinking that these information from ITypeInfo class are enough to create a java class which inherits the base class COMObject.
Which native stuff are you referring? Where do you see the trouble?

Tobias Wolf

unread,
Mar 15, 2013, 1:21:05 PM3/15/13
to jna-...@googlegroups.com
Currently I try to figure out how I can retrieve the Virtual Table ID of a method/function from a COM object. Any help would be appreciate!

Timothy Wall

unread,
Mar 15, 2013, 6:24:44 PM3/15/13
to jna-...@googlegroups.com
have you looked at the typelib parser included with com4j?

Ian Darby

unread,
Mar 16, 2013, 5:21:49 AM3/16/13
to jna-...@googlegroups.com
Hi Tobias,
 
I agree that auto-generation of the stub code would be a very worthwhile addition to the toolset.
 
I'll take a crack at solving the problem of unit testing and mocking out the IDispatch instance. As I have mentioned before I'm no expert on Windows but I'm sure that a bit of light reading will take me a long way. Plus of course the ocasional stupid question to yourself and Tim.
 
I'm pretty well tied up with other things this weekend but I'll download your updated code at my earliest opportunity and start playing.
 
By the way I'm not sure if there is an obscure bug in the COMObject(progID, useExisting) constructor, or if it is a problem with my Windows installation (though I have tried it on a second machine with same results). Some COM objects result in an error message about not being registered correctly (even though they work in VBA and VBScript). A good example is the WScript.Shell object which I encountered when providing hints on the thread about how to create a windows shortcut. You can use the short piece of code in that thread to quickly test it on your machine.
 
Regards,
Ian Darby

On Sunday, March 10, 2013 2:35:40 PM UTC, Ian Darby wrote:
Reply all
Reply to author
Forward
0 new messages