(JNI/JNA)java 调用c/c++ 动态链接库 全套操作+踩坑集锦
第一篇文章终于写完…跨行三年,一直都是看别人的文章…今天咱终于自己写了一篇,自己总结的,希望能给你一点点帮助,如有错误,希望指出,立马改正。
0 前言
Java代码是跨平台的,其与硬件环境彻底“隔离”,为了实现这个目的,JDK1.0开始就包含了一个本地方法接口,它允许JAVA程序调用C/C++写的程序,许多第三方的程序和JAVA类库。如:java.lang,java.io,java.net等都依赖于本地方法来访问底层系统环境的特征。但是这有两个问题:
1、本地方法想访问C中的结构(structures)一样访问对象中的字段。尽管如此,JVM规范并没有定义对象怎么样在内存中实现。如果一个给定的JVM实现布局对象时,和本地方法假设的不一样,那你就不得不重新编写本地方法库。
2、因为本地方法可以保持对JVM中对象的直接指针,所以,JDK1.0中的本地方法采用了一种保守的GC策略。
为了解决这两个问题,JDK1.1中出现了JNI,这就为java代码调用c/c++动态链接库提供了一种方法。
1 JNI
若调用本地的C/C++的动态链接库,我们首先要通过java实现自己的本地方法,我们只需要利用JNI提供的关键字native把方法声明为是本地方法(由其他语言是实现的方法),然后再利用c/c++实现一个有相同方法名的动态链接库,并放在指定目录下即可供Java调用即可。
Window环境下生成动态链接库为.dll,需要配置头文件.h,详细实现过程看这篇文章,非常详细,博主亲测可实现https://blog.csdn.net/weixin_51763233/article/details/122205288
Linux环境下生成动态链接库为.so,和windw环境差不多,详细过程看这篇文章,博主亲测可实现:https://zhuanlan.zhihu.com/p/465601205
总结一下调用分为五部:
- java类中申请一个本地方法
- 运行javah以获取包含该方法的C声明的头文件
- c/c++实现本地方法
- 讲dll/so放在共享类库中
- java程序加载该类库
注意:如果用c++实现本地方法,需要用extern ”C“来声明,这样是为了不让使用c++编译器来编译本地方法,因为c++编译器编译可能会给方法加上后缀,导致Java无法找到本地方法的实现。
extern "C" {void externC(int a ,int b){}
}
其实Java和c/c++不能互通的原因主要是数据类型的问题,jni解决了这个问题,jni定义了一系列JNI数据类型,Java和c/c++数据类型通过JNI定义的数据类型进行转换:
例如:在进行数字传递的时候,我们知道在不同的平台c中int型所占字节数是不一样的,所以JNI定义了jint;
又例如:那个c文件中的jstring数据类型就是java传入的String对象,经过jni函数的转化就能成为c的char*。
对应关系类型(部分):
Java类型 | JNI类型 | 描述 |
---|---|---|
boolean | jboolean | unsigned char |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
到此关于JNI,博主就介绍到这里,声明一点JNI是一个Java和其他语言互调的技术,也就是说你当然可以用JNI实现其他语言调用java程序,想继续深究的同学可以参考《Java核心技术(卷2)》最后一章”本地方法“。
2 JNA
JNI虽然实现了Java调用C/C++动态链接库,但是还是比较复杂的,尤其各种是数据类型的映射,于是乎,它来了-JNA(Java Native Access)。
JNA(Java Native Access )提供一组Java工具类用于在运行期间动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。
JNA可以让你像调用一般java方法一样直接调用本地方法。就和直接执行本地方法差不多,而且调用本地方法还不用额外的其他处理或者配置什么的,也不需要多余的引用或者编码,使用很方便。具体使用方法不在赘述,入门使用看这篇文章:https://blog.csdn.net/zhan107876/article/details/121051129
JNA虽然使用起来方便,但坑也着实的多:
JNA指针
java是值传递的,没有指针(地址)的概念,但是c/c++是有指针的,有时候我们需要把变量传递过去然后获取变量新的值,这个时候我们必须引入指针的概念。好在JNA中引入了Pointer(com.sun.jna.Pointer)。Pointer是JNA中引入的类,用来表示native方法中的指针。
native方法中的指针实际上就是一个地址,这个地址就是真正对象的内存地址。
举个例子(参考https://blog.csdn.net/zhan107876/article/details/121056384):如果我们需要调用一个动态链接库,其实现这样的一个函数:
/*** 返回a+b的值* 同时c和msg通过参数返回*/
int add(int a, int b, int *c, char **msg) {*c = (a + b) * 2;char *string = "hello world!";*msg = string;return a + b;
}
如果我们使用jna这么调用:
public class HelloJNA {/*** 定义一个接口,默认的是继承Library ,如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary* 这个接口对应一个动态链接(SO)文件*/public interface LibraryAdd extends Library {// 这里使用绝对路径加载LibraryAdd LIBRARY_ADD = Native.load("/program/cpp/libhello.so", LibraryAdd.class);int add(int a, int b, int c, String msg);}public static void main(String[] args) {int c = 0;String msg = "start";// 调用so映射的接口函数int add = LibraryAdd.LIBRARY_ADD.add(10, 15, c, msg);System.out.println("相加结果:" + add);}
}
那么不管add函数对c和msg做了何种改变,返回java中,值都不会被变更。
正确的调用方法是:
/*** 一个java类* 演示指针传输指针变量*/
public class HelloJNA_Pointer {/*** 定义一个接口,默认的是继承Library ,如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary* 这个接口对应一个动态链接(SO)文件*/public interface LibraryAdd extends Library {LibraryAdd LIBRARY_ADD = Native.load("/program/cpp/libhello.so", LibraryAdd.class);/*** 指针变量,用Pointer类型定义* c是int** msg是char***/int add_c(int a, int b, Pointer c, Pointer msg);}public static void main(String[] args) {Pointer c = new Memory(50);Pointer msg = new Memory(8);// 调用so映射的接口函数int add = LibraryAdd.LIBRARY_ADD.add_c(10, 15, c, msg);System.out.println("相加结果:" + add);// 指针变量System.out.println("c的值:" + c.getInt(0));// 这样才能拿到System.out.println("msg的值:" + msg.getPointer(0).getString(0));Native.free(Pointer.nativeValue(c)); //手动释放内存Pointer.nativeValue(c, 0); //避免Memory对象被GC时重复执行Nativ.free()方法Native.free(Pointer.nativeValue(msg)); //手动释放内存Pointer.nativeValue(msg, 0); //避免Memory对象被GC时重复执行Nativ.free()方法}
}
注意:在使用jna指针的时候需要首先申请一块内存
Pointer c = new Memory(50);
Pointer msg = new Memory(50);
最后在使用完之后手动的释放这片内存
Native.free(Pointer.nativeValue(c)); //手动释放内存
Pointer.nativeValue(c, 0); //避免Memory对象被GC时重复执行Nativ.free()方法
Native.free(Pointer.nativeValue(msg));
Pointer.nativeValue(msg, 0);
JNA结构体
java中没有结构体的概念,但是c/c++中结构体的使用非常频繁,为了解决这个问题,jna引入了类Structure ,如果在Java中定义一个结构体,只需要集成这个Structure 即可。Structure 初次使用,细节比较多,话不多说直接上代码,需要注意的地方会有注释:
//1:使用FieldOrder注解标注结构体成员的顺序,切记顺序一定不能弄错,
//不然结构体取出来的值会路透不对马嘴,因为结构体成员在内存中是依次排列的,
//如果顺序弄错jvm照着这个顺序按成员变量的类型取值肯定会错了
@Structure.FieldOrder(value= {"beginLoc","confidence","x","y","z","theta"})
public class PalletLocStruct extends Structure {//2:这个地方必须 public 不能privatepublic int beginLoc;public float confidence = 1;public int x = 0;public int y = 0;public int z = 0;public float theta = 0;//3:这个地方具体原理不知道,只知道声明之后,当前结构体可以按照引用或者值取值public static class ByReference extends PalletLocStruct implements Structure.ByReference {}public static class ByValue extends PalletLocStruct implements Structure.ByValue{}//4:这个地方表示当前结构体是内存对齐的,因为c/c++ 的结构体成员多数情况是内存对齐,//可以想象如果c/c++内存对齐,而java没有内存对齐,那取出来的值肯定也对不上public PalletLocStruct() {super(ALIGN_NONE);}
}
上面代码中1-4点要非常注意,还有一点需要注意:如果调用动态链接库直接return 结构体,那么取值会出现一个非常诡异的现象:“取结构体的第一个成员变量没问题,但是第二个成团变量就对不上了”,我的猜想原因应该是Java中结构体每个成员在内存中内存顺序不是连续的,导致第一个成员可以正常取值,但是到了第二个就不能正常取值。目前我还没找到解决方法,所以如果想要返回一个结构体,那么最好用指针的方式,把结构体传给动态链接库,然后再取出结构体里面的值。比如我在项目中这样处理:
c++
int palletDetection(palletLoc* palletLoc){palletLocs->confidence = 1;palletLocs->theta = 2;palletLocs->x = 3;palletLocs->y = 4;palletLocs->z = 5;}
java:
int palletDetection(PalletLocStruct palletLoc);取值:
palletLoc.beginLoc ...
JNA数组
Java中数组不是一块连续的内存地址,但是在c/c++中是连续的,所以我们传递Java数组的时候,一定要声明一块连续的内存,且要保证这块内存不会够用,比如传递一个结构体数组:
//为调.so文件规划一片连续的内存空间 长度位10
PalletLocStruct[] array = (PalletLocStruct[])new PalletLocStruct().toArray(10);
写在最后
本来感觉万事俱备只待算法,最后部署联调的时候,出现了这个问题:
java.lang.UnsatisfiedLinkError: Unable to load library '/javaServer/videoSchedule/libpalletDetector.so':
一般这个异常多数是路径设置不对导致,排查一下java加载.so文件路径是否正确或者看一下dll或so的位数是否和jdk一致(32/64)。但是我遇到的是另外一种情况,算法同时编译的.so引用了两外两个.so库,但是他编译的时候没有把这两个.so加进来,所以导致无法加载。这里顺便贴个链接,有需要可以看一下嵌套.so生成https://www.freesion.com/article/46841131235/
(JNI/JNA)java 调用c/c++ 动态链接库 全套操作+踩坑集锦相关推荐
- java调用c语言的动态库,hu-unix下面java调用c语言动态链接库.docx
PAGE / NUMPAGES glddydyzcw HU-unix下面java调用C语言动态链接库我在Hp-unix下面用java JNI怎么调用C语言的动态链接库??System.loadLibr ...
- 使用Java读取 “Python写入redis” 的数据踩坑记录
https://my.oschina.net/u/2338224/blog/3061507 使用Java读取 "Python写入redis" 的数据踩坑记录 https://seg ...
- JNA —— Java调用C/C++动态库
工作所需,要使用Java调用c/c++的动态库,实现Java程序使用动态库中的函数. 搜索了一番,常用的有JNI.JNA方法. JNI(Java Native Interface) JNI定义了一种公 ...
- android jni java调用c,Android与JNI(一) ---- Java调用C 静态调用
第一.通过eclipse新建一个工程名为HelloJni的android工程,并编译. 第二.右键工程-->Android Tools --> Add Native Support,出现如 ...
- jni开发-java调用c函数,c调用java函数实现
1.配置 下载ndk,cmake即可. 2.实现 MainActivity的实现 public class MainActivity extends AppCompatActivity {// Use ...
- JNI实现Java调用C代码Demo AndroidStudio
JNI(Java Native Interface)的本意是Java本地调用,它是为了方便java调用C/C++等本地代码所封装的一层接口 Android NDK(Native Development ...
- JNI:Java调用C接口的方法(1)
JNI基本概念: JNIEnv: Java的本地化环境.C/c++访问Java的对象.类等通过JNIEnv,它里面包含了所有的API.只要和Java交互的都需要它. JavaVM:Java虚拟机.在一 ...
- 【javascript】浏览器调用摄像头扫二维码踩坑记录
前言 最近做一个项目需要用浏览器调用摄像头扫二维码,然后就踩了几个坑记录下. 踩坑记录 我一开始发现了zxing这个库,他分为https://www.npmjs.com/package/@zxing/ ...
- 微信退款 java工具类,微信支付中退款踩坑记录
首先附上微信支付的开发者文档 其实这里所说的踩坑记录,无非就是微信在开发者文档上的写不太明确,也没有比较官方的demo,在此列出一个可行的demo,供大家下载使用. 主要问题就是在这几步解密上 微信的 ...
最新文章
- EM算法讲推导原理讲的懂了的,表达清晰易懂的, 收藏cnblog上的大牛的
- 我学到的C#——基础
- Flutter开发Flutter与原生OC、Java的交互通信-2(48)
- 谷歌大脑开源「数据增强」新招数:ImageNet准确率达85%,大神Quoc Le出品
- opengl纹理示例
- 如何看待Scrum Sprint Backlog冻结和变化?
- 几个常见的Python面试题,帮助大家更加从容面试!
- threejs加载模型挤压变形_车用水阀套零件冷挤压成形数值模拟试验研究
- Namomo Spring Camp Div2 Week1 - 第五次打卡
- The tempotron: a neuron that learns spike timing–based decisions 事件驱动
- Flask 源代码阅读笔记
- python selenium 获取元素下的元素个数_Python + Selenium,分分钟搭建 Web 自动化框架!(送自动化测试书籍)...
- vba 怎么取得一个book中最右边的sheet名_商业观察|探底新零售中的坪效革命
- Linux下禁止使用swap及防止OOM机制导致进程被kill掉
- (转)知乎-区块链技术:如何赋能供应链创新
- 四川大学计算机学院 学术委员会,学术委员会完整列表
- HG_REPMGR configure配置
- 专访 Livid:程序员值得花时间一看!
- 电脑桌面下栏和计算机里面全黑,电脑桌面下面菜单栏变黑条了,为什么?
- 快递鸟电⼦⾯单批量打印流程与注意事项
热门文章
- Windows Terminal 美化 / PowerShell 美化: oh-my-posh 主题安装和使用
- vue中网页图标favicon.ico不显示
- 大型网站技术架构·核心原理与案例分析 第八章·固若金汤:网站的安全架构思维导图
- 【无标题】star rain
- API管理的正确姿势--API Gateway
- 低耦合,高内聚的详解(绝对全面)
- Linux 学习 十三单元
- Maya Python 游戏与影视编程指南 第一章
- 常见Mbps、mb、Kbps、kb、bps单位换算-洋葱先生-杨少通
- F12开发人员工具如何使用、抓包、调试代码