(JNI/JNA)java 调用c/c++ 动态链接库 全套操作+踩坑集锦

您所在的位置:网站首页 java加载动态库失败怎么回事 (JNI/JNA)java 调用c/c++ 动态链接库 全套操作+踩坑集锦

(JNI/JNA)java 调用c/c++ 动态链接库 全套操作+踩坑集锦

2024-07-14 10:16| 来源: 网络整理| 查看: 265

第一篇文章终于写完…跨行三年,一直都是看别人的文章…今天咱终于自己写了一篇,自己总结的,希望能给你一点点帮助,如有错误,希望指出,立马改正。

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程序加载该类库

JAVA技术核心卷2 注意:如果用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类型描述booleanjbooleanunsigned charbytejbytesigned 8 bitscharjcharunsigned 16 bitsshortjshortsigned 16 bitsintjintsigned 32 bitslongjlongsigned 64 bitsfloatjfloat32 bitsdoublejdouble64 bitsvoidvoidN/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 不能private public 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/



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3