Search code examples
javacomjna

Mapping a COM interface method in JNA


I am trying to understand how Native.loadLibrary works, but I can not find a good explanation on the web.

I need to get access to the IVssBackupComponents::AbortBackup function witch is located in VssApi.lib. Here is a link for the function docs: https://learn.microsoft.com/en-us/windows/win32/api/vsbackup/nl-vsbackup-ivssbackupcomponents

My code is like this:

public interface MyVssClass extends WinNT, StdCallLibrary
{
    MyVssClass INSTANCE = Native.loadLibrary("VssApi", MyVssClass.class);

    public int AbortBackup();
}

But I get Error looking up function 'AbortBackup': The specified procedure could not be found. Which is obvious since I need somehow indicate that this function is in separate interface in this library.

How do I solve this issue?


Solution

  • Your code to load the VssApi DLL is correct. However, the AbortBackup() function is a function on a COM object with the IVssBackupComponents interface.

    Mapping COM objects is a bit challenging but doable. JNA's COM support includes a way to invoke functions on COM objects, requiring 4 bits of information:

    • A pointer to the object. This is obtained from another function, either in the conventional C API exposed by the DLL or from another COM function.
    • A "pointer" to the function itself. This is something called the VtblId.
    • An array of arguments to the function. (Java primitives or objects.)
    • A Java object representing the return value.

    In your example, first, you have to locate the API function that will instantiate the object. In your case that appears to be the CreateVssBackupComponents() function. So you'll map that in your VssApi interface like this:

    public interface MyVssClass extends WinNT, StdCallLibrary
    {
        MyVssClass INSTANCE = Native.loadLibrary("VssApi", MyVssClass.class);
    
        HRESULT CreateVssBackupComponents(PointerByReference ppBackup);
    }
    

    Of note (and this is probably always the case for COM objects), that API tells you that you are responsible for releasing the object when you are done with it, so make sure to do this!

    The calling application is responsible for calling IUnknown::Release to release the resources held by the returned IVssBackupComponents when it is no longer needed.

    Mapping the COM object methods can be done inheriting from the Unknown class in JNA's COM mappings (class VssBackupComponents extends Unknown { ... }) , which implements the IUnknown interface. Inheriting gives you the Release() method, for example (you can peek inside the Unknown class to see the implementation).

    Unknown also exposes _invokeNativeObject() , _invokeNativeInt(), and _invokeNativeVoid() methods that you can map either directly or with a "wrapper" class. Check out the Wbemcli.java class in the JNA project for an example of a few direct and indirect mappings. The arguments are an array of objects, the return type is straightforward with ample examples.

    The hard part is finding the VtblId which will let JNA find the COM object's address of the actual function.

    The original C header (vsbackup.h) for this class has a IVssBackupComponentsVtbl structure containing a list of functions. The VtblId is the order of these functions. 0, 1, and 2 match the 3 functions in IUnknown.

    I am unable to find a copy of vsbackup.h online, but I did locate this mapping for Rust, which isn't as authoritative as the original API, but I suspect is consistent, begins the function counting (after IUnknown's 0, 1, and 2) at index 3. The AbortBackup() function would then appear to be index 15. (Please check this with another source if you can.) So your final mapping should look something like this (completely untested):

    class VssBackupComponents extends Unknown {
    
        public VssBackupComponents() {
        }
    
        public VssBackupComponents(Pointer p) {
            super(p);
        }
    
        public HRESULT AbortBackup() {
            // 16th method (MAYBE?) in IVssBackupComponentsVtbl
            return (HRESULT) this._invokeNativeObject(15,
                new Object[] { this.getPointer() }, HRESULT.class);
        }
    }
    

    Then in your main code, you'll call the function to get a ponter to the COM object and instantiate it as follows:

    PointerByReference ppBackup = new PointerByReference();
    MyVssClass.INSTANCE.CreateVssBackupCompontents(ppBackup);
    // you should really test the HRESULT of the above line...
    
    VssBackupComponents backup = new VssBackupComponents(ppBackup.getValue());
    // You have an object now! Do stuff with it
    try {
        // ... stuff ...
        backup.AbortBackup();
        // you probably want to test HRESULT
        // and do whatever else ...
    } finally {
        backup.Dispose();
    }