j-Interop

(Release 1.23 Beta, 25th Dec 2007)



Implementation of DCOM wire protocol (MSRPC) to enable development of Bi-Directional, Pure and Non-Native Java applications which can interoperate with any COM component . The implementation is not dependent on JNI for COM interoperability. It also allows for complete Windows Registry manipulation operations (create,read,update,delete) using the WinReg interface.More information about j-Interop is available here.

Contents


  1. Getting Started
  2. Frequently Asked Questions
  3. Third Party Dependencies
  4. Installation
  5. Sample usage
  6. Examples
  7. License
  8. Third Party Licenses
  9. Support
  10. Acknowledgements


Getting Started

First, please follow the installation instructions. Once done, the best way to get started is by looking at the examples. You may want to skip the FAQs with [Advance] tag for the time being. You can get back to them once you are a bit comfortable with j-Interop.

Please also have a look at the sample usage section.

If the COM server you are trying to access has a Windows COM client (executed from a Remote machine), then it is probably already configured for DCOM access. If it is not so, please read FAQ (A5). If the COM server you are using is a DLL\OCX (In-Proc), you may want to have a look at FAQ (A6).

Some important things, before you start:

If you are not really familiar with DCOM, then this article provides a good overview. The architecture document can be found here.


Frequently Asked Questions

  1. What is the JRE version compatibility of j-Interop ?
  2. How do I install it ?
  3. What threading model do the COM servers adhere to while in use from j-Interop ? [Advance]
  4. What threading model does j-Interop follow ? [Advance]
  5. How do I configure my COM server for DCOM access ?
  6. My COM Server is a DLL\OCX (Inproc server), how do I make it work with j-Interop ?
  7. Why could the library not do step 7 for me automatically?
  8. What all COM Interfaces are directly supported ?
  9. What data types are supported by j-Interop ?
  10. COM to Java Data Type Mappings [Advance]
  11. Do I have to do any reference counting,memory management etc. ?
  12. Any configuration to be done before using j-Interop ?
  13. Is there any logging done ?
  14. What type of License does it follow ?

Answers

(A1) Tested with JRE 1.4.2_05 and 1.5.0_05 on WinXP,2K Adv.Server,2K3 and Fedora(2.6.15-1.2054_FC5). Compatible with JRE version 1.3.1 and above.

(A2) Please see the third party dependencies and installation section.

(A3) From MSDN:-

On Windows side, Local servers (EXEs) are in full control of the kind of apartment(threading model) that COM is using for their objects. The local server calls CoInitializeEx on one or more threads and registers the class factory (or multiple class factories) from the appropriate thread with the appropriate threading model.

The In-process servers (DLLs), however run in the context of their client i.e they run in the apartment the client gives them. By client, I don't mean j-Interop here, but a Windows COM Client. In-process components indicate the threading model they are ready to satisfy by placing a named value (ThreadingModel) under their InprocServer32 key in the registry:

[HKEY_CLASSES_ROOT\CLSID\{clsid}\InprocServer32]
"ThreadingModel"="Both"

or

"ThreadingModel"="Apartment"

or

"ThreadingModel"="Free"

If ThreadingModel is not specified, the component is assumed to follow the assumptions for "STA-Main" and can only be loaded into the main STA in a process. A value of "Both" indicates that the component can be loaded in both MTAs and STAs. A value of "Apartment" indicates that the component can be loaded into any STA. A value of "Free" indicates that the component can be loaded into an MTA, but not into an STA.

(A4) j-Interop follows the "Apartment" model i.e regardless of threading model or the component type(Local or Inproc) used by the COM Server, j-Interop synchronizes all calls to the COM Server per org.jinterop.dcom.core.JIComServer instance (which is the starting point for each COM Server).

For e.g. in the following piece of code :-

JISession session = JISession.createSession("DOMAIN","USERNAME","PASSWORD");
JIComServer comServer = new JIComServer(JIProgId.valueOf(session,"Excel.Application"),"127.0.0.1",session);
IJIUnknown unknown = comServer.createInstance();
IJIDispatch dispatch = (IJIDispatch)ComFactory.createCOMInstance(ComFactory.IID_IDispatch,unknown);
IJITypeInfo typeInfo = dispatch.getTypeInfo(0);
FuncDesc funcDesc = typeInfo.getFuncDesc(0);
int dispId = dispatch.getIDsOfNames("Visible");
JIVariant variant = new JIVariant(Boolean.TRUE);
dispatch.put(dispId,variant);

Calls from all interfaces ("dispatch" and "typeInfo"), even if they are running on different threads, are synchronized at the JIComServer("comServer") level. That said, Within an application, there can be more than one JIComServers running at the same time and they run independent of each other.

(A5) Ideally if your COM server is actively being used for remote access , then it is perhaps already configured for DCOM. If not you can configure it by following steps mentioned here or here.

For Windows XP (SP2) , this is a good link.

Alternatively, this is also a good article.

(A6) Ideally if your COM server is actively being used for remote access , then it is perhaps already configured for DCOM. If not, you have 2 ways to do this. Both ways are recommended by Microsoft. I personally prefer the Easiest way.

Please note that the use of surrogates for accessing DLLs is not a j-Interop specification, but a COM specification. In any DCOM case for accessing a DLL\OCX you would need the DLLHOST. It is a Microsoft DCOM DLL Host Process. If the COM server being accessed is an Exe, like MSWord or MSExcel or IE then this is not required. But for DLLs , it is required. This is how the DCOM clients talk to In-Process Servers. You can obtain more info from here (please open in IE for proper viewing).

Also, it would be best to view j-Interop as a DCOM client when accessing COM from Java. It would be much easier to work with it then. Whatever configurations are required for a DCOM client, will be required for j-Interop also.

(A7) j-Interop behaves as a COM client to the COM Server, changes in step 6 have to be done at the server machine's registry. It is best that the user initiate those actions instead of the library doing these silently.

(A8) All automation interfaces like IDispatch, ITypeInfo, ITypeLib, IEnumVariant are directly supported. You can start using them right away.

(A9) All DCOM datatypes including VARIANTs are supported by j-Interop. The only limitation in the present version is that Arrays upto Maximum 2 dimensions are accepted currently.

(A10) After going through some of the examples, it should be fairly simple(or so I hope) to guage what COM data type maps to which Java type, but here are some hints anyways:-

  1. All primitive data types of COM map to their corresponding Java counterparts, with the exception of long, which maps to int at the Java side.
  2. IIDs are j-Interop uuids (represented as java.lang.String).
  3. OLECHAR is JIString(LPWSTR).
  4. Top level pointers are pointers that are NOT elements of arrays, NOT members of structures or unions. All Top level "Interface" pointers (IUnknown*, IDispatch*, IDispatch**, ITypeLib** etc.) are mapped to there referents themselves.
    For e.g :-
    ITypeLib** ppTLib or ITypeInfo* ppTInfo maps directly to JIInterfacePointer and NOT to JIPointer(JIInterfacePointer).
  5. All Other Top level pointers are mapped as following:-
    1. First level indirection is mapped directly to there referents. for e.g. int* is int, double* is double, BSTR* is JIString(BSTR_FLAG) ,OLECHAR FAR * ptr is JIString(LPWSTR).
    2. Second and subsequent level indirections are mapped to there (level - 1) indirections. for e.g. int** maps to JIPointer(int), int*** maps to JIPointer(JIPointer(int)), double** maps to JIPointer(double), double*** maps to JIPointer(JIPointer(double)).
    3. All data types can be mapped like rule iv and v(a,b).The exception to above rules are BSTR** and VARIANT**. Since BSTR and VARIANTs in COM are inherently pointers themselves, they follow rule v(b) only after 3rd level of indirection.
      i.e. BSTR* and BSTR** are both mapped to JIString(BSTR). VARIANT* and VARIANT** are both mapped to JIVariant(,byRef=true); 3rd and subsequent level indirections of BSTR or VARIANTs are mapped according to rule (level - 2). for e.g. the BSTR*** mapped to JIPointer(JIString(BSTR)) , VARIANT*** is mapped to JIPointer(JIVariant(,byRef = true));
  6. When using IJIDispatch, you will be required to use JIVariants. Automation in COM does not allow indirection beyond level 2. So simple mappings would suffice for non pointer types, and for pointer types as parameters, please use the byRef flag of JIVariant.

    Most of the times the MSDN documentation itself will tell you what the data type stands for, just use the corresponding type in j-Interop. For e.g.

    From MSDN:-

    IDispatch::GetIDsOfNames HRESULT GetIDsOfNames( REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgDispId );

    riid is Reserved for future use. Must be IID_NULL.

    rgszNames is Passed-in array of names to be mapped.

    cNames is Count of the names to be mapped.

    lcid is The locale context in which to interpret the names

    rgDispId is Caller-allocated array, each element of which contains an identifier (ID) corresponding to one of the names passed in the rgszNames array. The first element represents the member name. The subsequent elements represent each of the member's parameters.

    j-Interop definition for these would be:-


  7. Embedded Pointers (members of structures,unions, elements of arrays) are represented by JIPointer(type), like the OLECHAR FAR* FAR* rgszNames in Point (vi). It first got mapped to JIArray since it is a top level pointer. Within the array , it was supposed to return pointers to OLECHAR, therefore it got mapped to JIPointer(JIString(LPWSTR)) forming the whole definition as JIArray(JIPointer(JIString(LPWSTR))).

    For more examples, please have a look at JITypeInfoImpl.java (getFuncDesc API). It will show you how the mapping is done between embedded pointers and j-Interop types. Please keep MSDN handy for having a look at the actual C++ struct. Search on ITypeInfo -->GetFuncDesc(...) there. Also see the FUNCDESC structure.

  8. Unless specified otherwise(like the dimensions are provided) in the documentation, all arrays are conformant arrays.
  9. You can also look up the IDL for the component if only size_is() is present then Array "isConformant" (true) . if both size_is() and length_is() are present then Array "isConformant" and "isVarying" (true,true) .
  10. When you are implementing callbacks, if the COM source interface has a single level interface pointer like IDispatch* then mapping to local java class will be JIInterfacePointer itself. But second level pointers to interfaces like IDispatch** must be declared as JIVariants only.

    for e.g.:-
    IDispatch* can be mapped to IJIDispatch , but IDispatch** should be mapped to JIVariant only.

    see MSInternetExplorer --> DWebBrowserEvents (BeforeNavigate2 and NewWindow3) for more details.

(A11) No, the library does all this on it's own (including pinging the COM server for keeping it alive).

(A12) Yes, please make sure that the Server service is running on the target workstation (where the COM Server is hosted). This is required for reading the registry to map the ProgIds to their clsids. If you can't have this , then please use clsid instead of progId. The progIdVsClsidDB.properties maintains a mapping of progId Vs there clsids, if this file is present in the classpath. This file is consulted before the registry for the progId.

Also, if you are working with GUI components and would like to make them visible\interactive, then make sure that you read up (A5) and setup the COM Server for "Interactive User". By default, it is the "Launching User". If this option is set, then the COM Server will not present it's GUI. It is best to use this for all silient operations like working with DBs , or using Excel formulas etc.

(A13) Yes, j-Interop uses java logging by default (and to the console), but you can configure a handler for this logger to redirect output to logger mechanisms of your own choice. There is also a method in JISystem.setInBuiltLogHandler which creates a handler to store the logs to a file in the user's temp directory as j-Interop.log . (e.g. for Windows systems it should be "C:\Documents and Settings\your_username\Local Settings\Temp")

(A14) Please see the license section.


License

This library is distributed under the LGPL (http://www.opensource.org/licenses/lgpl-license.php). Please refer to lgpl.txt for more details.


Third Party Dependencies

j-Interop depends on :-

Installation

Please extract all files from the j-Interop.zip. The "lib" folder has all the jars required to use j-Interop library.

Installation of j-interop.jar is similar to any standard jar file, just add it to the classpath.

As a performance improvement, the j-Interop comes with a progIdVsClsidDB.properties file, which gets updated with the clsid of each ProgId. This prevents the library from accessing the Windows Registry for the clsid, when the same COM server is required again. Please make sure that progIdVsClsidDB.properties is also included in the classpath.


Sample Usage

j-Interop provides JIComServer as the embodiment of the actual COM server. This serves as the start point for interoperating with COM.

You can either use a ProgID (well known name for the COM server like "Excel.Application" or "InternetExplorer.Application") or use the Clsid directly (IID of the interface).

Typically, you would always want to use the IDispatch interface (Automation).

//First establish a session. All calls happen within the purview of the Session.
//for localhost , you can use "localhost" and "administrator" (since they have universal access)
//JISession session = JISession.createSession("localhost","administrator","PASSWORD");

JISession session = JISession.createSession("DOMAIN","USERNAME","PASSWORD");
//Now create the Com server to the target machine, this could either be an IP address or a hostname.
JIComServer comServer = new JIComServer(JIProgId.valueOf(session,"Excel.Application"),"127.0.0.1",session);
//Instantiate the COM Server
IJIUnknown unknown = comServer.createInstance();
//Get a handle to it's IDispatch
IJIDispatch dispatch = (IJIDispatch)ComFactory.createCOMInstance(ComFactory.IID_IDispatch,unknown);
//call an API
JIVariant variant = new JIVariant(Boolean.TRUE);
//This will show excel(if yours is not visible then follow FAQ A12).
dispatch.put("Visible",variant);

... //when your work is done
JISession.destroySession(session); //optional step, the session will be destroyed anyways when the application exits //but good to do when you are working with many COM servers and are done with a few.

For users working with COM Servers not supporting Automation, they can work directly with the APIs. They would need to know the operation numbers of the APIs they would be calling on the COM Server. These can easily be obtained from the IDL or from the Type Library ("Ole View" for viewing type libraries comes with Microsoft Visual Studio Tools).

The part about obtaining the JIServer and getting to the unknown is common in the code above, but instead of using IJIDispatch you would be going ahead like this:-

//Now QueryInterface for the required interface
//In this example "620012E2-69E3-4DC0-B553-AE252524D2F6" is the interface I would like to use of the COM Server
IJIComObject handleOfInterface = (IJIComObject)unknown.queryInterface("620012E2-69E3-4DC0-B553-AE252524D2F6");

//Set up the call Object, this object carries your params back and forth between the COM Server and the Java App.
JICallObject callObject = new JICallObject(handleOfInterface.getIpid());

//The same object can be used for many calls, please make sure you reinit before usage/call.
callObject.reInit();
// set up the obtained operation number, in our case it is 137.
callObject.setOpnum(137);
//This is the input param, ours has only 1 , but if yours have more than they have to be added sequentially and in order.
//These are available in the TypeLibrary\IDL, look for [in] params.
callObject.addInParamAsString("Hello", JIFlags.FLAG_REPRESENTATION_STRING_BSTR);
//This is the expected result, you can find out about the return types from the TypeLibrary\IDL as well.
//Look for [out] params.
callObject.addOutParamAsObject(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_BSTR),JIFlags.FLAG_NULL);
//Execute the call on the obtained interface.
Object[] results = handleOfInterface.call(callObject);

Please make sure that you call JISession.destroySession(...) after you are done away with the usage of your session. This will close the sockets to the COM server.

For attaching callbacks , please look at the examples MSInternetExplorer and MSSysInfo.


From MSInternetExplorer

The JIJavaCOClass is a representation for a Java server class. It's there so that when we get to the next version of the library, I am able to support full bi-directional access. Currently, you can implement any IDL of an existing COM server using the JIJavaCOClass and pass it's interface pointer instead of the original COM server and it will work fine. Similar mechanism is exploited for call backs.In our case I had to implement DWebBrowserEvents interface.

IJavaCoClass javaComponent = new JIJavaCoClass(new JIInterfaceDefinition("45B5FC0C-FAC2-42bd-923E-2B221A89E092"),DWebBrowserEvents2.class);

This definition create a Java component with an IID of 45B5FC0C-FAC2-42bd-923E-2B221A89E092...I just made this one up for uniquely classifying this class...you can equate this to a lib identifier of COM IDL. This is required if there are multilple interfaces being implemented in the same Java Class. If you have only one...you can put it's IID here. I just did not do it for showing the user a possiblity.
The JIJavaCOClass has the option of instantiating the DWebBrowserEvents.class or it could use another ctor to pass an already instantiated object. In latter scenario, the object would be used as target for the events instead of instantiating a new one from DWebBrowserEvents.class.

Now that we have a Java server, we need to define the methods\events it will handle. This is done using the Method descriptors which are themselves described using the Parameter Objects.

JIParameterObject propertyChangeObject = new JIParameterObject();
This creates a Parameter Object, capable of defining a IN or OUT type for a Method.

like:-
propertyChangeObject.addInParamAsType(JIString.class,JIFlags.FLAG_NULL);

JIMethodDescriptor methodDescriptor = new JIMethodDescriptor("PropertyChange",0x70,propertyChangeObject);
javaComponent.getInterfaceDefinition().addMethodDescriptor(methodDescriptor);

This declares a method descriptor. The first parameter in the ctor is the API name of the api to implement, the second one is it's OP number.This one can be obtained from the IDL\TypeLib. And the third param is the parameterObject describing the input\output types of this method.
If you do not want to use this ctor, there is another, which sequentially increments the method numbers starting from 1.The calls below add a new interface IID to this Java server. It simply means that the server supports this interface definition.

ArrayList list = new ArrayList();
list.add("34A715A0-6587-11D0-924A-0020AFC7AC4D");
javaComponent.setSupportedEventInterfaces(list);

This will be the list of all COM interfaces which this Java class supports or implements.

The next call attaches the event handler (our JIJavaCoClass) to the actual COM server for recieving events for the interface identified by the IID.
There can be many such calls on the same COM server for different IIDs.

identifier = ComFactory.attachEventHandler(ieObject,"34A715A0-6587-11D0-924A-0020AFC7AC4D",JIInterfacePointer.getInterfacePointer(session,javaComponent));

Now whether you use IJIDispatch or not, events will work regardless of that. The COM object you have to use in the attachEventHandler is the COM Object on
which you did the queryinterface for the IJIDispatch.


Examples

Commented examples are located in the "examples" folder.

Third Party Licenses


Support

If you have any issues, please mail project admin directly or file a bug in the bugs database maintained at project website.

Acknowledgements

This product includes software developed by the iWombat.com and all other members of the Third Party Dependencies.