Search code examples
javacomjnacom4jimapi

Can JNA be used for a complex Windows DLL like IMAPI


I've managed to get COM4J to use some functionality in the windows IMAPI (CD writing).

However I've failed to get any of the calls that return SAFEARRAYs working, but this project doesn't appear to be currently active ...

The DLL is usually in C:\Windows\System32\imapi2.dll, and using it also requires using C:\Windows\System32\imapi2fs.dll

Looking around for a JAVA-COM bridge project that is active led me to JNA.

The remit of the project to simplify JAVA-COM bridging intrigued me .... however I fell at the first hurdle, and am hoping someone can help.

So far I've taken the Microsoft IMAPI examples and written a Powershell application, from which I have the series of calls I need to make to the API.[CDInterface][1]

The first thing you need to do with IMAPI is create an Instance of IDiskMaster2, so I've declared that via an Imapi2 interface, like so

public interface Imapi2 extends Library {
        Imapi2 INSTANCE = (Imapi2)
                Native.load("C:/Windows/System32/imapi2.dll" , Imapi2.class);

        public static class IDiscMaster2 extends Structure {
            int getCount;

            public int getCount() {
                return getCount;
            }
        }
        IDiscMaster2 createMsftDiscMaster2();
    }

Then in the main code

 Imapi2.IDiscMaster2 recorderList = Imapi2.INSTANCE.createMsftDiscMaster2();
        System.out.println("Found " + recorderList.getCount() + " Recorders");

Just putting 'imapi2' in the call to Native.load() didn't work either.

I'm guessing I'm doing something fundamentally wrong, but it's not clear how you get JNA to 'see' a new dll you want to interface to ..... and also I am kind of afraid there is something very different about this API from the othe APIs that people are using JNA to talk to, so may not be worth trying!

public interface Imapi2 extends Library {
        Imapi2 INSTANCE = (Imapi2)
                Native.load("C:/Windows/System32/imapi2.dll" , Imapi2.class);

        public class IDiscMaster2 extends Dispatch {

            public static final CLSID CLSID_MsftDiscMaster2 = new CLSID("2735412F-7F64-5B0F-8F00-5D77AFBE261E");

            public IDiscMaster2() {
            }

            private IDiscMaster2(Pointer pvInstance) {
                super(pvInstance);
            }

            public static IDiscMaster2 create() {
                PointerByReference pbr = new PointerByReference();

                WinNT.HRESULT hres = Ole32.INSTANCE.CoCreateInstance(CLSID_MsftDiscMaster2, null, WTypes.CLSCTX_ALL, null, pbr);
                if (COMUtils.FAILED(hres)) {
                    System.out.println("ERROR: Failed to create instance");
                    return null;
                }

                return new IDiscMaster2(pbr.getValue());
            }

            public WinNT.HRESULT _getCount(Pointer count ){
                return (WinNT.HRESULT) _invokeNativeObject(2, new Object[]{count}, WinNT.HRESULT.class);
            }

            public long getCount() {
                try {
                    long count = -1;
                    Pointer ptr = new Pointer(count);
                    WinNT.HRESULT result = _getCount(ptr);

                    COMUtils.checkRC(result);

                    return count;
                } catch ( Exception e ) {
                    System.out.println("Error : " + e.getMessage());
                }
                return -1;
            }
} 

Then invocation in main changed to

Imapi2 imapi2Lib = Imapi2.INSTANCE;
        Imapi2.IDiscMaster2 recorderList = new Imapi2.IDiscMaster2();

        System.out.println("Found " + recorderList.getCount() + " Recorders");

IntelliJ shows up uninvoked methods, so it doesn't look like create() is getting called. Not sure if this is because I need to call it, or down to the function implementing IDispatch not IUnknown. [1]: https://github.com/nosdod/CDInterface


Solution

  • I've answered this in a similar question which I originally marked this as a duplicate of. However, given the difficulty loading this, your case is unique enough that I'll attempt to give a separate answer.

    The general case for COM is that there is an API function that creates the object. You have mapped this as createMsftDiscMaster2(). Note that you have allocated a resource here and it needs to be disposed of when you are done with it; the API documentation should tell you how to do that (possibly by calling Release() from IUnknown.)

    Your next step is to map the IDiscMaster2 COM class. I see two mappings here, so I'm confused as to which one you want. The one at the top of your question is incorrect, but the one extending Dispatch later is the correct way to start, but I'm not clear where you've gone after that. The rest of the class should look similar to the internals of the Dispatch class in JNA.

    In that class you can see the boilerplate that you will follow. Note that it extends Unknown which follows the same boilerplate for offsets 0, 1, and 2 for the first 3 COM functions QueryInterface, AddRef, and Release. Dispatch picks up with offsets 3, 4, 5, and 6 for COM functions GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, and Invoke.

    So in your mapping for DiskMaster2 you will pick up with offset 7, and your mapping will look like:

    public HRESULT TheFunctionName(FOO foo, BAR bar) {
        return (HRESULT) this._invokeNativeObject(7,
                new Object[] { this.getPointer(), foo, bar },
                HRESULT.class);
    }
    

    This is where you need to locate the actual header file for this class to determine the order in which the functions appear in the Vtbl. It looks like you attempted to do this with your code, but the offset 2 is already assigned in Unknown, the lowest one you'll be able to use is 7 (and continue on with 8, 9, 10 for each function in this COM interface, in the correct order -- which you must determine from the Vtbl.)

    Based on this header, you can see those functions mapped in order and your offsets should be: 7: get__NewEnum, 8: get_Item, 9: get_Count, and 10: get_IsSupportedEnvironment. Use those header function mappings as a start and change them to the _invokeNativeObject() format above. (They all return HRESULT, you'll just be changing the argument list.)