1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > (JNI/JNA)java 调用c/c++ 动态链接库 全套操作+踩坑集锦

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

时间:2018-12-21 16:38:27

相关推荐

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

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

0 前言

Java代码是跨平台的,其与硬件环境彻底“隔离”,为了实现这个目的,JDK1.0开始就包含了一个本地方法接口,它允许JAVA程序调用C/C++写的程序,许多第三方的程序和JAVA类库。如:java.lang,java.io,等都依赖于本地方法来访问底层系统环境的特征。但是这有两个问题:

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,详细实现过程看这篇文章,非常详细,博主亲测可实现/weixin_51763233/article/details/122205288

Linux环境下生成动态链接库为.so,和windw环境差不多,详细过程看这篇文章,博主亲测可实现:/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*。

对应关系类型(部分):

到此关于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方法一样直接调用本地方法。就和直接执行本地方法差不多,而且调用本地方法还不用额外的其他处理或者配置什么的,也不需要多余的引用或者编码,使用很方便。具体使用方法不在赘述,入门使用看这篇文章:/zhan107876/article/details/121051129

JNA虽然使用起来方便,但坑也着实的多:

JNA指针

java是值传递的,没有指针(地址)的概念,但是c/c++是有指针的,有时候我们需要把变量传递过去然后获取变量新的值,这个时候我们必须引入指针的概念。好在JNA中引入了Pointer(com.sun.jna.Pointer)。Pointer是JNA中引入的类,用来表示native方法中的指针。

native方法中的指针实际上就是一个地址,这个地址就是真正对象的内存地址。

举个例子(参考/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文件规划一片连续的内存空间 长度位10PalletLocStruct[] 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生成/article/46841131235/

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。