Getting an opaque struct returned by parameter in

2019-07-27 08:23发布

问题:

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.

回答1:

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.