zoukankan      html  css  js  c++  java
  • JNA 使用总结

    JNA 是基于 JNI(Java Native Interface) 技术的开源工具,能够实现单方向的 Java 调用本地方法(通常是 C/C++ 编写的动态链接库中的函数),在 Windows 中是 .dll 文件,Linux 下是 .so 文件。类似于 JNA 这种跨语言调用的工具还有 JNative 和 SWING 等

    JNI 和 JNA 的介绍网络上比较多,本文记录的是自己在使用 JNA 过程中遇到的一些问题及相应的解决办法。注:开发环境为 Windows,IDE 为蒸汽大脑的 IDEA

    DLL 文件路径

    在调用 DLL 前,我们首先要做的事情就是确认 DLL 的版本和 JDK 的版本是否一致,必须严格区分 32 位和 64 位,否则即使文件路径是正确的,也会抛出无法加载文件的错误

    DLL 位数,可以通过 VS 的 CMD 命令dumpbin /headers xxx.dll来查看

    1. IDEA 设置

      • 直接放在src路径下面
      • 放在指定路径,然后在ModulesDependencies中添加JARs or directories选中该文件夹路径
    2. JDK bin 目录下 JRE 的 bin 路径
      这里要注意的是路径为 JDK 目录下的 JRE,如C:Program Files (x86)Javajdk1.8.0_171jrein

    3. java.library.path
      实际上就是要将 DLL 文件放在java.library.path路径下,可以通过-Djava.library.path参数在 JVM 启动时指定自定义路径,不过该方法不够方便
      另外,程序启动后再修改 JVM 的该属性是无效的
      获取当前应用的java.library.path

      System.getProperty("java.library.path");
      

      参考:

    4. 使用的是 JNA 框架的话还可以通过设置jna.library.path,让 JNA 加载指定位置的 DLL,具体用法如下:

      //启用 JNA 加载 DLL 文件 DEBUG 日志
      System.setProperty("jna.debug_load", "true");
      //根据当前的类路径设置 JNA 查找 DLL 文件路径,
      String jnaLibPath = Main.class.getResource("").getPath();
      jnaLibPath = jnaLibPath.substring(0, jnaLibPath.indexOf("特殊路径"))
              .replaceFirst("/", "") + "dll";// 绝对路径
      System.setProperty("jna.library.path", jnaLibPath);
      

      相关的日志如下:

      Looking in classpath from sun.misc.Launcher$AppClassLoader@b4aac2 for /com/sun/jna/win32-x86/jnidispatch.dll
      Found library resource at jar:file:/D:/"特殊路径"/lib/jna-4.5.2.jar!/com/sun/jna/win32-x86/jnidispatch.dll
      Looking for library 'HCNetSDK'
      Adding paths from jna.library.path: "指定路径"
      Trying "指定路径"HCNetSDK.dll
      Found library 'HCNetSDK' at D:"路径"HCNetSDK.dll
      

      以上动态的设置 jna.library.path,通过命令启动也可以设置 java -Djna.library.path=<path to your library> MainClass

    回调函数的使用

    1. 在链接库文件对应的接口中,声明回调函数的接口
      回调函数的接口需要继承Callback或者是StdCallCallback
    2. 定义回调方法
      一般方法名称都是 invoke,这个好像是无所谓的,但是参数一定要跟提供的文档,或者头文件严格一致(对应类型和顺序)
    3. 具体实现
      一般是写一个具体类实现接口,然后重写对应的回调方法,有一点需要特别注意的是,应该将实现类的实例设置为成员变量,因为局部变量会被 GC,那么回调方法只会在开始的时候调用几次

    输出参数

    Java 中只有输入参数和一个返回值,当需要有多个返回时,一般都用集合或者封装成对象来返回。但是 C/C++ 不一样,有输入参数,输出参数,返回值

    个人理解:输出参数功能跟返回值差不多,由函数外部定义,传入函数内部,一般都是指针

    由于 Java 中是没有指针的,所以我们用 Pointer 类的实例来代替,下面是示例:

    //内存不够会导致 JVM 崩溃,EXCEPTION_ACCESS_VIOLATION
    Pointer onlineIpList = new Memory(1024 * 5);
    int sum = PkV10AccessDll.INSTANCE.PKV10_OnlineList(onlineIpList);
    String onLineStr = onlineIpList.getString(0);
    logger.info("初始在线控制器数量:" + sum + " " + onLineStr);
    //释放内存
    long peer = Pointer.nativeValue(onlineIpList);
    Native.free(peer);
    //避免再次调用该方法
    Pointer.nativeValue(onlineIpList, 0);
    

    注意:为 Pointer 实例分配的内存是在 Native Heap 中的,GC 是管不了的,需要自己手动释放内存

    数据类型的匹配

    这是整个 JNA 使用过程中,比较难的部分

    下面列举一些工作中遇到的一些问题:

    1. char *
      C/C++ 中有两种const char *char *,一般使用 String 应该就可以了,但有时会报错:非法内存访问。可以试一下字节数组来解决这个问题,在获取字符串的字节数组时需要注意编码问题
    2. unsigned数据处理
      Java 中的数据类型都是有符号的,所以在传递无符号整数时,可以用范围更大一点的数据类型去接收返回值
    3. 结构体
      1. 结构体中定义的所有字段都需要用public修饰
        参数的顺序必须严格参照头文件或者文档定义

        在 JNA 4.* 以后的版本中,通过getFieldOrder()方法返回的字段名列表,JNA 在使用时,会计算结构体的大小,为其分配内存。在getFieldList()方法中会通过反射来获取所有的public修饰的字段。如果获取的结果与getFieldOrder()方法返回结果不匹配则会抛出异常,详见 Structure 1000 行代码,JNA Version 为4.5.2

      2. 结构体作为输出参数指针
        实例化一个结构体对象后,通过实例的write()方法来为其分配内存。在调用使用该结构体的方法时,参数设置为实例的getPointer()返回的指针。最后通过实例的read()方法获取数据

    4. 数组
      1. 二维数组
        数组是行优先的,二维数组按总长度对应成 Java 中的一维数组,示例:
        // public int[][] a = new int[2][3]; 错误
        public int[] a = new int[2 * 3];  // 正确
        
      2. 结构体中定义结构体数组
        直接看示例:
        public NET_DVR_SCHEDTIME[] struAlarmTime = (NET_DVR_SCHEDTIME[]) new NET_DVR_SCHEDTIME().toArray(10);
        
      3. 二维的结构体数组
        结合前面说到的两种方式,示例:
        public NET_DVR_SCHEDTIME[] struAlarmTime = (NET_DVR_SCHEDTIME[]) new NET_DVR_SCHEDTIME().toArray(MAX_DAYS * MAX_TIMESEGMENT);
        

    参考

    1. JNA 提供的数据结构映射示例
    2. JNA 4.5.2 API
    3. 海康威视和大华官网提供了 SDK ,可以参考其 JNA 或 JNI 使用示例
  • 相关阅读:
    开源数据库
    深度学习TensorFlow笔记——学习率
    深度学习TensorFlow笔记——损失函数
    深度学习TensorFlow笔记
    Oracle常用内置函数
    Oracle数据库自带表或者视图
    Oracle数据库查询所有关键字
    IP代理网址
    时间、日历(time、calendar、datatime)
    selenium常用操作
  • 原文地址:https://www.cnblogs.com/magexi/p/11008992.html
Copyright © 2011-2022 走看看