Search code examples
javacpointersstructjna

Getting an opaque struct returned by parameter in JNA


I'm trying to call a method in Apple's Security.h framework that returns a struct by reference, like so:

int findSomething(SomeStruct *s)

(Specifically, it's this method, and I'm trying to get itemRef. There's a usage example here.) The problem is that I don't know what fields SomeStruct has or how big it is. It only exists to be passed to other native library functions. So, I want something like this (Java):

interface SomeLib extends Library {
    int findSomething(Pointer p);
}

...
Pointer p = ... // Not sure how to make this
nativeLib.findSomething(p)
// Do something with p

If I could do sizeof(SomeStruct) in Java, I could create the pointer using JNAs Memory, I think. I could write a native method to return sizeof(SomeStruct), but I don't want to add a native component to my own code.

This is similar to this question, but it asks about a case where the fields of SomeStruct are known at runtime, whereas, in my case, the fields are obscured intentionally by the library authors.


Solution

  • The SecKeychainItemRef type is defined to be a pointer to the struct. This means that the SecKeychainFindGenericPassword function actually expects a pointer to a pointer as the itemRef argument, and as such, you can use the JNA PointerByReference class as the argument.

    After a successful call, you can use PointerByReference.getValue() to get the opaque pointer.

    /* int SecKeychainFindGenericPassword(
     *     Pointer keychainOrArray,
     *     int serviceNameLength,
     *     String serviceName,
     *     int accountNameLength,
     *     String accountName,
     *     IntByReference *passwordLength,
     *     PointerByReference passwordData,
     *     PointerByReference itemRef
     * );
     */
    
    static void main() {
        IntByReference passLength = new IntByReference(0);
        PointerByReference passwordData = new PointerByReference();
        PointerByReference itemRef = new PointerByReference();
    
        int result = SecKeychainFindGenericPassword(
            keychainOrArray,
            "service name".length(),
            "service name",
            "account".length(),
            "account",
            passLength,
            passwordData,
            itemRef
        );
    
        if (result == 0) {
            System.out.printf(
                "OSStatus: %d, passDataPtr: 0x%08X, itemRefPtr: 0x%08X%n",
                result,
                Pointer.nativeValue(passwordData.getValue()),
                Pointer.nativeValue(itemRef.getValue())
            );
        } else {
            /* Use SecCopyErrorMessageString to get a human-readable message */
            System.out.printf("An error occurred: %d%n", result);
        }
    }
    

    If you're calling this method in an actual project, I would suggest creating a class named SecKeychainItemRef which extends the PointerByReference class. This communicates the argument's type to the reader in a clearer fashion, even if it doesn't let you access the internals of the struct.