JNA实现JAVA调用C/C++动态库

1.JNA

JNA全称Java Native Access,是一个建立在经典的JNI技术之上的Java开源框架(https://github.com/twall/jna)。JNA提供一组Java工具类用于在运行期动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。

JNA在线帮助文档:https://github.com/java-native-access/jna/blob/master/www/WindowsDevelopmentEnvironment.md
下载地址:https://github.com/java-native-access/jna/tags

2.数据类型

Java和C++的数据类型对照表

C++Java
char *String
wordshort
bytebyte
byte[]byte[]
dwordint
longNativeLong
Void *Pointer
lpvoidPointer
lpDwordIntByReference
HWNDHWND
char[]byte[]
byte *Pointer

Java和C的数据类型对照表

Java类型C类型原生表现
booleanint32位整数(可定制)
bytechar8位整数
charwchar_t平台依赖
shortshort16位整数
intint32位整数
longlong,__int6464位整数
floatfloat32位浮点数
doubledouble64位浮点数
Buffer/Pointerpointer平台依赖(32或64位指针)
没有pointer/array32或64位指针(参数/返回值)邻接内存(结构体成员)
Stringchar*/0结束的数组(nativeencodingorjna.encoding)
WStringwchar_t*/0结束的数组(unicode)
String[]char**/0结束的数组的数组
WString[]wchar_t**/0结束的宽字符数组的数组
Structurestruct*/struct指向结构体的指针(参数或返回值)(或者明确指定是结构体指针)结构体(结构体的成员)(或者明确指定是结构体)
Unionunion等同于结构体
Structure[]struct[]结构体的数组,邻接内存
Callback(*fp)()Java函数指针或原生函数指针
NativeMappedvaries依赖于定义
NativeLonglong平台依赖(32或64位整数)
PointerTypepointer和Pointer相同
int (usually)enum枚举类型

3.使用

3.1 Maven依赖

        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>5.9.0</version>
        </dependency>

3.2 加载动态库

1.创建动态库对应的接口类

    public interface RobotApi extends Library {
        /*
         *@brief 创建机器人连接实例,返回机器人连接实例指针
         *@param index(in):所创建的机器人连接索引,从0递增,最大为max_connection_count - 1
         *@return CRobotAPI* p:空类型的机器人连接实例指针
         */
        CRobotAPI.ByReference CreateCRobotAPI(int index);


        /*
         *@brief 初始化并连接机器人
         @param pRobot(in):指向被操作的机器人连接实例的指针
         *@param robot_addr(in):机器人IP地址
         *@return 0:连接成功,非0:错误码
         */
        int ConnectRobot(CRobotAPI.ByReference pRobot,Memory robot_addr);

        /*
         @param pRobot(in):指向被操作的机器人连接实例的指针
         *@brief 断开机器人连接
         */
        int DisconnectRobot(CRobotAPI.ByReference pRobot);

        /*
         * 机器人上电
         *@param pRobot(in):指向被操作的机器人连接实例的指针
         *@return 0:上电成功,非0:错误码
         */
        int PowerOn(CRobotAPI.ByReference pRobot);

        /*
         * 机器人上电
         *@param pRobot(in):指向被操作的机器人连接实例的指针
         *@return 0:上电成功,非0:错误码
         */
        int PowerOff(CRobotAPI.ByReference pRobot);
    }

注意

  • 接口名、参数、返回值要和动态文件相匹配,对应类型参考步骤二
  • 接口类要继承Library或者StdCallLibrary

3.3 接口实例化

因为想要将动态库文件放入自定义文件夹下,所以加载目录设置成自定义目录,下文有介绍
getIndex()方法为获取接口所需参数,无需关注,主要看整体使用流程

@Slf4j
@Service
public class RobotApiService {

    private static RobotApi instance;

    @Value("${manage.file.path}")
    private String path;
    
    @PostConstruct
    public void init(){
        String name = path;
        String os = System.getProperty("os.name").toLowerCase();
        String arch = System.getProperty("os.arch").toLowerCase();
        if (os.contains("win")) {
            // 根据实际的 Windows 动态库名称进行替换
            name = path + File.separator +"RobotAPI";
        } else if (os.contains("mac")) {
            // 根据实际的 macOS 动态库名称进行替换
        } else if (os.contains("nix") || os.contains("nux") || os.contains("aix")) {
            // 根据实际的 Linux 动态库名称进行替换
            name = path + File.separator +"libRobotAPI.so";
        } else {
            throw new UnsupportedOperationException("不支持的操作系统: " + os + " " + arch);
        }
        instance = Native.load(name, RobotApi.class);
    }
    public CRobotAPI.ByReference createCRobotAPI(int index){
        return instance.CreateCRobotAPI(index);
    }
    public int powerOn(int id){
        return instance.PowerOn(getIndex(id));
    }

    public int powerOff(int id){
        return instance.PowerOff(getIndex(id));
    }

    public int connectRobot(String robotAddr,int index){
        CRobotAPI.ByReference reference = createCRobotAPI(index);
        StaticLog.info("指针地址{}",reference);
        return instance.ConnectRobot(reference,mem);
    }


    /**
     * 断开机器人连接
     */
    public int disconnectRobot(int index){
        CRobotAPI.ByReference pRobot = getIndex(index);
        return instance.DisconnectRobot(pRobot);
    }
    
    private static void freeMemory(Memory mem) {
        long peer = Pointer.nativeValue(mem);
        //手动释放内存
        Native.free(peer);
        //避免Memory对象被GC时重复执行Nativ.free()方法
        Pointer.nativeValue(mem, 0);
    }
}

配置文件

#服务配置
manage:
  file:
    path: D:\workspace\jni-jna-web-master\dll

3.4 控制类

@RestController
@RequestMapping("/robot")
public class RobotController {
    @Resource
    private RobotApiService apiService;

    /**
     * 连接机械臂
     *
     * @return 控制指针
     */
    @GetMapping("/connectRobot")
    public int connectRobot(int index) {
        String addr = "127.0.0.1";
        return apiService.connectRobot(addr,index);
    }
    
    /**
     * 断开机器人连接
     */
    @GetMapping("/disconnectRobot")
    public int disconnectRobot(int index) {
        return apiService.disconnectRobot(index);
    }
}

3.5验证

启动项目
在这里插入图片描述
访问接口
在这里插入图片描述
在这里插入图片描述
成功调用动态库接口

4.注意事项

  • 动态库版本选择要和当前操作系统相匹配,windows和linux不能共用,还要注意操作系统位数,32和64也不能混用。
  • JAVA调用C++代码时需要将动态库编译成C语言格式的,否则C++会修改默认的接口名称导致JNA调用失败。
  • 在使用指针的情况下要手动释放,java是值传递的,没有指针(地址)的概念,但是c/c++是有指针的,所以Pointer是JNA中引入的类,用来表示native方法中的指针,不被JVM所管理需要手动释放。
  • Windows环境下可以通过Native.load(path,class)方法直接加载dll文件,linux环境下需要将自定义的文件目录加入到系统配置中,否则无法读取so文件。
  • 如果动态库返回结构体实例指针则需要创建对应的类去接收,下文有介绍。

5.指南

5.1 Linux下读取SO文件

原理:

JVM在载入动态库时候,会从java.library.path所指定的目录下开始查找,找不到就会报动态库缺少的错误。此外,如果动态库a.so依赖于b.so,则jvm在加载a.so之前,会先加载b.so。也就是说,如果a.so和b.so不在一个目录下,即使在加载a.so时,指定了目录,也会报动态库缺少错误。

增加动态库目录常用方法有两种

  • 修改ld.so.conf文件
    用文本编辑器打开/etc/ld.so.conf或/etc/ld.so.conf.d/下的配置文件(可能需要sudo权限)。

    sudo vim /etc/ld.so.conf

    在文件末尾添加新的动态库目录路径(每个目录一行)。

    /your/custom/library/path

    保存并关闭文件。

    运行ldconfig来更新动态链接器的缓存。

    sudo ldconfig

  • 使用LD_LIBRARY_PATH环境变量
    你可以临时地通过设置LD_LIBRARY_PATH环境变量来添加动态库目录。

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/your/custom/library/path
    #查看当前环境变量
    echo $LD_LIBRARY_PATH

查看动态库依赖
使用ldd命令可以查看应用程序或动态库的依赖。
在这里插入图片描述
可以查看当前动态库是否缺少依赖,上图缺少GLIBCXX_3.4.21,需要安装,否则无法启动

5.2 接收动态库返回结构体指针并在其他接口中使用

1.JNA模拟结构体

C语言中的定义

class CRobotAPI{
private:
	unsigned int m_robot_index;
	char* m_server_addr;
	bool m_connected;
	unsigned int m_wobj_num;  //自定义工件坐标系个数,可取值32-200,默认200
};

在java中的模拟

@Data
public class CRobotAPI  extends Structure {

    public int m_robot_index;
    public String m_server_addr;
    public boolean m_connected;
    //自定义工件坐标系个数,可取值32-200,默认200
    public int m_wobj_num;


    // 定义值传递和指针传递类
    public static class ByReference extends CRobotAPI implements Structure.ByReference {
        //指针和引用的传递使用ByReference
    }

    public static class ByValue extends CRobotAPI implements Structure.ByValue {
        //拷贝参数传递使用ByValue
    }

    /**
     * 重写getFieldOrder获取字段列表, 很重要,没有会报错
     */
    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList("m_robot_index", "m_server_addr", "m_connected", "m_wobj_num");
    }
}

代码说明与使用总结

  • C、C++中的结构体成员默认是public的,无需使用public关键字修饰,而在Java的实现中必须使用public关键字修饰,否则会报成员不存在的错误。

  • 在Java中模拟C、C++的结构体时数据类型的映射必须一一对应,成员属性的名字可以不同(但一般为了方便追踪问题,建议定义成和.h头文件中一致)。

  • 在Java中定义的成员属性的顺序必须和C、C++中头文件结构体中定义的顺序一致,不能调整顺序,否则会引起异常。

  • 必须使用@Structure.FieldOrder或者重载FieldOrder方法,并返回一个字符串数组,数组中的成员是结构体的成员属性列表。

  • 关于内存对齐,如果结构体中使用了内存对齐,那么在java中也必须声明一个无参构
    造函数,并调用父类方法指定内存对齐模式,否则有可能引发内存访问异常。

  • 如果需要用到结构体指针,则需要在结构体类的实现中实现静态内部类ByReference,如代码中所示,CRobotAPI .ByReference 就等同于 CRobotAPI *

  • 如果需要用到结构体对象数组,则需要在结构体类的实现中实现静态内部类ByValue,如上述代码所示, CRobotAPI .ByValue 就等同于 CRobotAPI []

动态库返回结构体实例指针
在这里插入图片描述
需要上一步返回的结构体实例指针作为参数传递回去
在这里插入图片描述
在这里插入图片描述

实际过程中可能需要多次使用该指针,可以保存到当前线程内共多次使用。
在这里插入图片描述

5.3 调用C++编译的动态库

如果用c++实现本地方法,需要用extern ”C“来声明,这样是为了不让使用c++编译器来编译本地方法,因为c++编译器编译可能会给方法加上后缀,导致Java无法找到本地方法的实现。

extern "C" {
   void externC(int a ,int b){}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/249264.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络——数据链路层-可靠传输的实现机制:回退N帧协议GBN(无差错情况、累积确认、有差错情况、发送窗口尺寸)

目录 回退N帧协议GBN 介绍 无差错情况 累积确认 有差错情况 发送窗口尺寸 小结 练习 解析 示意图 上篇中所介绍的停止-等待协议的信道利用率很低&#xff1b;若出现超时重传&#xff0c;则信道利用率更低。 如果发送方在收到接收方的确认分组之前可以连续发送多个数…

Leetcode—2413.最小偶倍数【简单】

2023每日刷题&#xff08;六十&#xff09; Leetcode—2413.最小偶倍数 class Solution { public:int smallestEvenMultiple(int n) {return (n % 2 1) * n;} };运行结果 之后我会持续更新&#xff0c;如果喜欢我的文章&#xff0c;请记得一键三连哦&#xff0c;点赞关注收藏…

局域网环境下的ntp对时

服务端&#xff1a; 此处为v4-sp4服务器 安装ntp&#xff0c;apt-get install ntp -y ,若为离线环境&#xff0c;则安装ntp和libopts25两个包。 配置&#xff1a; 在/etc/ntp.conf的配置文件里 加入 restrict default nomodify notrap noquery restrict 127.0.0.1 rest…

libxlsxwriter - 编译

文章目录 libxlsxwriter - 编译概述笔记编译环境编译思路编译安装组件写个测试程序, 看看编译的组件是否好使END libxlsxwriter - 编译 概述 想换一个新版的libxlsxwriter, 自己编译一个出来. libxlsxwriter依赖zlib, 前面已经成功编译了zlib(zlib - 编译). 笔记 libxlsxwr…

大模型Transformer 推理 :kvCache原理浅析

大模型Transformer 推理 :kvCache原理浅析 kvCache 原理 在采样时,Transformer模型会以给定的提示/上下文作为初始输入进行推理(可以并行处理),然后逐一生成额外的标记来继续完善生成的序列(体现了模型的自回归性质)。在采样过程中,Transformer会执行自注意力操作,为…

若依 ruoyi-vue3 集成aj-captcha实现滑块、文字点选验证码

目录 0. 前言0.1 说明 1. 后端部分1.1 添加依赖1.2. 修改 application.yml1.3. 新增 CaptchaRedisService 类1.4. 添加必须文件1.5. 移除不需要的类1.6. 修改登录方法1.7. 新增验证码开关获取接口1.8. 允许匿名访问 2. 前端部分&#xff08;Vue3&#xff09;2.1. 新增依赖 cryp…

C++中STL的概念——零基础/小白向,适合竞赛,初学C++者使用

目录 1.STL的诞生 2. STL的基本概念 3. STL六大组件 4. STL容器&#xff0c;算法&#xff0c;迭代器 容器&#xff1a;存放数据的地方 算法&#xff1a;解决问题的方法 迭代器&#xff1a;容器和算法之间的桥梁 5. STL初始&#xff1a;打印0 ~ 9 的数字 这篇文章是一篇…

day34算法训练|贪心算法

1005.K次取反后最大化的数组和 两次贪心算法思路 1. 数组中有负数时&#xff0c;把绝对值最大的负数取反 2. 数组全为非负数时&#xff0c;一直取反最小的那个数 步骤&#xff1a; 第一步&#xff1a;将数组按照绝对值大小从大到小排序&#xff0c;注意要按照绝对值的大小…

云仓酒庄为您甄选西班牙葡萄酒

西班牙是一个拥有悠久葡萄酒酿造与饮用历史的国家&#xff0c;其葡萄酒产量位居世界第三位。云仓酒庄的品牌雷盛红酒分享翻开西班产区地图&#xff0c;不少葡萄酒刚入门的朋友会感到头疼&#xff0c;众多产区、分级制度、陈年标准&#xff0c;想要短时间内搞懂实在不容易。不用…

案例069:基于微信小程序的计算机实验室排课与查询系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

设计模式-状态(State)模式

目录 开发过程中的一些场景 状态模式的简单介绍 状态模式UML类图 类图讲解 适用场景 Java中的例子 案例讲解 什么是状态机 如何实现状态机 SpringBoot状态自动机 优点 缺点 与其他模式的区别 小结 开发过程中的一些场景 我们在平时的开发过程中&#xff0c;经常会…

【C语言(十五)】

动态内存管理 一、为什么要有动态内存分配? 我们已经掌握的内存开辟方式有&#xff1a; int val 20 ; // 在栈空间上开辟四个字节 char arr[ 10 ] { 0 }; // 在栈空间上开辟 10 个字节的连续空间 但是上述的开辟空间的方式有两个特点&#xff1a; • 空间开辟大小是固…

leetcode LCR 173. 点名

代码&#xff1a; class Solution {public int takeAttendance(int[] records) {int left0,rightrecords.length-1;while (left<right){int midleft(right-left)/2;if(midrecords[mid]){leftmid1;}else {rightmid;}}if(leftrecords[left]){return left1;}else {return left…

北斗三号短报文+4G的低功耗太阳能船载报位监控方案

国内海洋船舶群体长期在海上航行&#xff0c;多数海员由于海面无信号覆盖、个人卫星通信费用昂贵、无法自由使用船载公用卫星通信设备等原因&#xff0c;无法与家人和朋友保持联系&#xff0c;甚至在遇到危险的时候也无法及时向外界发出求救信号&#xff0c;管理单位难以掌握船…

新钛云服助力爱达邮轮·魔都号首航,保驾护航,共创辉煌

随着2024年1月1日的临近&#xff0c;中国首艘国产大型邮轮——爱达邮轮魔都号即将迎来激动人心的首航时刻。作为爱达邮轮的IT系统运维和安全服务伙伴&#xff0c;新钛云服有幸提前登船体验&#xff0c;并为魔都号即将到来的航行提供全面的技术支持与保障。 爱达魔都号&#xff…

微积分-三角函数2

三角函数 在上一节中&#xff0c;讨论了如何在直角三角形中定义三角函数&#xff0c;限制让我们扩展三角函数的定义域。 事实上我们可以取任意角的正弦和余弦&#xff0c;而不只是局限于 0 0 0~ π 2 \frac{\pi}{2} 2π​当中。 当然需要注意的是&#xff0c;正切函数对不是对…

Git使用rebase和merge区别

Git使用rebase和merge区别 模拟环境使用merge合并使用rebase 模拟环境 本地dev分支中DevTest增加addRole() 远程dev被同事提交增加了createResource() 使用merge合并 使用idea中merge解决冲突后, 推送远程dev后,日志图显示 使用rebase idea中使用功能rebase 解决冲突…

论文解读 | NeurIPS2023:「解释一切」图像概念解释器

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 讲者简介 孙奥&#xff1a; 香港科技大学软件安全实验室在读博士&#xff0c;研究兴趣为可解释性人工智能和可信机器学习&#xff0c;主要是从Post-hoc&#xff0c;逻辑和概念的角度分析神经网络的机理 Title 「…

IntelliJ IDEA 自带的 HTTP Client接口调用插件,替代 Postman

文章目录 引言建议目录结构新建请求不同环境的变量配置添加环境http-client.env.jsonhttp-client.private.env.json引用变量 请求示例Get请求示例Post请求示例鉴权示例断言示例Websocket请求示例 内置对象和动态变量内置对象&#xff1a;内置变量&#xff1a; 引言 在日常的 W…

Eslint 要被 Oxlint替换了吗

什么是 Oxlint 由于最近的rust在前端领域的崛起,基于rust的前端生态链遭到rust底层重构,最近又爆出OxLint,是一款基于Rust的linter工具。Oxlint在国外前端圈引起热烈讨论,很多大佬给出了高度评价。 事实上,Oxlint 是 Oxc 项目旗下的一款产品,专为 JavaScript 和 TypeSc…