通过 void** 参数作为 char[] 与 JNA 获取函数结果

Get result of function by void** parameter as char[] with JNA

TL;DR:我将什么 JNA 类型用于 void** 指针(实际上是 char**),以及如何访问它的结果?

我正在尝试使用 this C function:

从 Java 中的 macOS Keychain 获取密码
OSStatus SecKeychainFindGenericPassword(CFTypeRef keychainOrArray,
        UInt32 serviceNameLength,
        const char *serviceName,
        UInt32 accountNameLength,
        const char *accountName,
        UInt32 *passwordLength,
        void * _Nullable *passwordData,
        SecKeychainItemRef  _Nullable *itemRef);

(Usage example.)

问题是,我不确定如何处理 passwordData 参数。我设法从 C 调用了它(参见 post 的底部)。但是,在 Java 中,我得到了 passwordData 参数的不确定且不正确的结果。这是我尝试从 Java:

调用它
import com.sun.jna.*;
import com.sun.jna.ptr.*;

public class JnaTest {

    public interface Security extends Library {
        int SecKeychainFindGenericPassword(
                Object keychainOrArray,
                int serviceNameLength,
                String serviceName,
                int accountNameLength,
                String accountName,
                IntByReference passwordLength,
                // I've also tried char[][] and Pointer, which both give errors
                PointerByReference passwordData,
                Object itemRef);
    }

    public static void main(String[] args) {
        Security sec = Native.loadLibrary("Security", Security.class);

        PointerByReference pass = new PointerByReference();
        IntByReference len = new IntByReference();
        int rc = sec.SecKeychainFindGenericPassword(
                null,
                10, "SurfWriter",
                10, "MyUserAcct",
                len, pass,
                null);

        System.out.println(rc); // 0, good
        System.out.println(len.getValue()); // 11, same as in C
        // This prints Unicode characters nondeterministically; buffer overrun?
        System.out.println(new String(pass.getValue().getCharArray(0, len.getValue())));
    }

}

我能够编写这个 C 程序来做同样的事情,而且它工作正常,所以问题几乎肯定是在 JNA 中读取 passwordData:

#include <Security/Security.h>

int main() {
    unsigned int len;
    void* pass;
    int rc = SecKeychainFindGenericPassword(
            NULL,
            10, "SurfWriter",
            10, "MyUserAcct",
            &len, &pass,
            NULL);
    printf("%d %s\n", len, pass);
}

我无法对其进行测试(因为我目前正在使用 Windows),但是如果您尝试像这样检索密码会怎样:

String password = new String(pass.getValue().getPointer(0).getCharArray(0, len.getValue()));

这只是一个想法,为了获得更有用的答案,我需要调试代码。

还是很纳闷,为什么returns长度是11

事实证明,如果我这样做,它会起作用:

pass.getValue().getString(0);

有效。完美。不幸的是,这不是一个完整的解决方案,因为出于安全原因,我想避免将密码存储在 Strings 中。又玩了一会儿,我发现这行得通:

new String(pass.getValue().getByteArray(0, len.getValue()-4));

-4 是因为 len 由于某种原因太大了 4;我在 Security.h 的文档中找不到任何关于此的内容。)我意识到它之前打印的 "random" 密码在后半部分只是随机的,这使我得出结论:Java uses 16-bit chars internally,所以我得到的每个字符实际上都是两个字符,我阅读了必要的两倍。 Java 将 2 个 ASCII 字符视为一个 UTF-16 字符。 (JNA 不应该处理这个吗?)