首页 > 解决方案 > 本机库在某些 JVM 上无法正常工作

问题描述

我正在尝试使用 JNA 在 java 中使用本机库。库加载,我可以调用方法,但在某些 JVM 发行版上,本机库的行为不符合预期。一些结果:

JDK distributions              Behaves as expected
Oracle JDK 1.8.0_191           yes
Liberica JDK 1.8u192           no
Liberica JDK 1.8u275           no
AdoptOpenJDK OpenJ9 1.8u272    yes
AdoptOpenJDK OpenJ9 15.0.1     no
AdoptOpenJDK Hotspot 1.8u275   no
AdoptOpenJDK Hotspot 14.0.1    no

这里预期的行为是通过获取硬件信息LC_get_hardware_info然后退出而没有错误。

似乎没有 JDK9+ 发行版可以工作,一些 JDK8 发行版可以工作,有些不能(编辑)所有经过测试的 JVM 都是 64 位的。

代码(编辑:不调用LC_get_hardware_info不会改变行为,只是在成功的情况下没有输出),返回的值SenseLC是错误代码:

public static void main(String[] args) {        
    System.setProperty("jna.library.path", System.getProperty("user.dir"));

    SenseLC lib = SenseLC.INSTANCE;

    PointerByReference handle = new PointerByReference(new Memory(8));
    
    int ret = lib.LC_open(0, 0, handle);
    if (ret != SenseLC.LC_SUCCESS) {
        System.out.println("Open: " + SenseLCUtil.getCodeName(ret));
        return;
    }
    System.out.println("Handle: " + handle.getValue());

    LC_hardware_info hi = new LC_hardware_info();
    ret = lib.LC_get_hardware_info(handle.getValue(), hi);
    if (ret != SenseLC.LC_SUCCESS) {
        System.out.println("Read: " + SenseLCUtil.getCodeName(ret));
        return;
    }

    System.out.println(hi);

    ret = lib.LC_close(handle.getValue());
    if (ret != SenseLC.LC_SUCCESS) {
        System.out.println("Close: " + SenseLCUtil.getCodeName(ret));
        return;
    }

}

SenseLCUtil.getCodeName只是将返回码映射到字符串

预期行为:

所有对SenseLCreturn 0( LC_SUCCESS) 的调用,在调用 之后ret = lib.LC_get_hardware_info(handle.getValue(), hi);System.out.println(hi);打印:

LC_hardware_info{
developerNumber={somenumber}
serialNumber={somenumber}
setDate={somenumber}
reservation=0
}

失败行为:

调用int ret = lib.LC_open(0, 0, handle);成功(ret=0调用后),运行后ret = lib.LC_get_hardware_info(handle.getValue(), hi); ret=3LC_INVALID_PARAMETER),程序输出为:

Read: LC_INVALID_PARAMETER

似乎某些 JDK 上的本机代码返回了不正确的句柄。

编辑 1:添加 LC_hardware_info 和 SenseLC 类

LC_hardware_info:

@FieldOrder({
    "developerNumber",
    "serialNumber",
    "setDate",
    "reservation"
})
public class LC_hardware_info extends Structure {

    public int developerNumber;
    public byte[] serialNumber = new byte[8];
    public int setDate;
    public Pointer reservation;

    public String getSerial() {
        return new String(serialNumber, StandardCharsets.UTF_8);
    }

    @Override
    public String toString() {
        return "LC_hardware_info{"
            + "\ndeveloperNumber=" + developerNumber
            + "\nserialNumber=" + getSerial()
            + "\nsetDate=" + setDate
            + "\nreservation=" + reservation
            + "\n}";
    }

}

感测LC:

public interface SenseLC extends Library {
 
    int LC_SUCCESS = 0; // Successful
    int LC_OPEN_DEVICE_FAILED = 1; // Open device failed
    int LC_FIND_DEVICE_FAILED = 2; // No matching device was found
 
    int LC_INVALID_PARAMETER = 3; // Parameter Error
    int LC_INVALID_BLOCK_NUMBER = 4; // Block Error
    int LC_HARDWARE_COMMUNICATE_ERROR = 5; // Communication error with hardware
    int LC_INVALID_PASSWORD = 6; // Invalid Password
    int LC_ACCESS_DENIED = 7; // No privileges
    int LC_ALREADY_OPENED = 8; // Device is open
    int LC_ALLOCATE_MEMORY_FAILED = 9; // Allocate memory failed
    int LC_INVALID_UPDATE_PACKAGE = 10; // Invalid update package
    int LC_SYN_ERROR = 11; // thread Synchronization error
    int LC_OTHER_ERROR = 12; // Other unknown exceptions
 
    SenseLC INSTANCE = (SenseLC) Native.load("Sense_LC", SenseLC.class);
 
    int LC_open(int vendor, int index, PointerByReference handle);
    int LC_close(Pointer handle);
    int LC_passwd(Pointer handle, int type, byte[] passwd);
    int LC_read(Pointer handle, int block, byte[] buffer);
    int LC_write(Pointer handle, int block, byte[] buffer);
    int LC_encrypt(Pointer handle, byte[] plaintext, byte[] ciphertext);
    int LC_decrypt(Pointer handle, byte[] ciphertext, byte[] plaintext);
    int LC_set_passwd(Pointer handle, int type, byte[] passwd, int retries);
    int LC_change_passwd(Pointer handle, int type, byte[] oldpasswd, byte[] newpasswd);
    int LC_get_hardware_info(Pointer handle, LC_hardware_info info);
    int LC_get_software_info(LC_software_info info);
    int LC_Hmac(Pointer handle, byte[] text, int textlen, byte[] digest);
    int LC_Hmac_software(byte[] text, int textlen, byte[] key, byte[] digest);
    int LC_update(Pointer handle, byte[] buffer);
    int LC_set_key(Pointer handle, int type, byte[] key);
    int LC_gen_update_pkg(byte[] serial, int block, byte[] buffer, byte[] key, byte[] uptPkg);
 
}

编辑 2库头文件: lc.h,部分sdk (Clave2)

编辑 3:更新了 JNA 映射,问题仍然存在。旧代码可以在这里找到

标签: javajava-native-interfacejna

解决方案


您在不同的 JVM 上得到不同结果的事实表明存在未定义的行为。在它看起来有效的情况下,你只是走运了。

正如评论中所讨论的,您的问题可能是lc_handle_t. 头文件表明它是 64 位机器上的指针大小,以及int32 位机器上的大小。因此,您应该PointerByReference在看到* lc_handle_t时使用(打开以获取手柄时),然后getValue()在返回的手柄上使用lc_handle_t.

该类型也出现在结构的reservation字段中LC_hardware_info,应该映射到Pointer.


推荐阅读