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

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

总结一下调用分为五部:

  1. java类中申请一个本地方法
  2. 运行javah以获取包含该方法的C声明的头文件
  3. c/c++实现本地方法
  4. 讲dll/so放在共享类库中
  5. 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++ 动态链接库 全套操作+踩坑集锦相关推荐

  1. java调用c语言的动态库,hu-unix下面java调用c语言动态链接库.docx

    PAGE / NUMPAGES glddydyzcw HU-unix下面java调用C语言动态链接库我在Hp-unix下面用java JNI怎么调用C语言的动态链接库??System.loadLibr ...

  2. 使用Java读取 “Python写入redis” 的数据踩坑记录

    https://my.oschina.net/u/2338224/blog/3061507 使用Java读取 "Python写入redis" 的数据踩坑记录 https://seg ...

  3. JNA —— Java调用C/C++动态库

    工作所需,要使用Java调用c/c++的动态库,实现Java程序使用动态库中的函数. 搜索了一番,常用的有JNI.JNA方法. JNI(Java Native Interface) JNI定义了一种公 ...

  4. android jni java调用c,Android与JNI(一) ---- Java调用C 静态调用

    第一.通过eclipse新建一个工程名为HelloJni的android工程,并编译. 第二.右键工程-->Android Tools --> Add Native Support,出现如 ...

  5. jni开发-java调用c函数,c调用java函数实现

    1.配置 下载ndk,cmake即可. 2.实现 MainActivity的实现 public class MainActivity extends AppCompatActivity {// Use ...

  6. JNI实现Java调用C代码Demo AndroidStudio

    JNI(Java Native Interface)的本意是Java本地调用,它是为了方便java调用C/C++等本地代码所封装的一层接口 Android NDK(Native Development ...

  7. JNI:Java调用C接口的方法(1)

    JNI基本概念: JNIEnv: Java的本地化环境.C/c++访问Java的对象.类等通过JNIEnv,它里面包含了所有的API.只要和Java交互的都需要它. JavaVM:Java虚拟机.在一 ...

  8. 【javascript】浏览器调用摄像头扫二维码踩坑记录

    前言 最近做一个项目需要用浏览器调用摄像头扫二维码,然后就踩了几个坑记录下. 踩坑记录 我一开始发现了zxing这个库,他分为https://www.npmjs.com/package/@zxing/ ...

  9. 微信退款 java工具类,微信支付中退款踩坑记录

    首先附上微信支付的开发者文档 其实这里所说的踩坑记录,无非就是微信在开发者文档上的写不太明确,也没有比较官方的demo,在此列出一个可行的demo,供大家下载使用. 主要问题就是在这几步解密上 微信的 ...

最新文章

  1. EM算法讲推导原理讲的懂了的,表达清晰易懂的, 收藏cnblog上的大牛的
  2. 我学到的C#——基础
  3. Flutter开发Flutter与原生OC、Java的交互通信-2(48)
  4. 谷歌大脑开源「数据增强」新招数:ImageNet准确率达85%,大神Quoc Le出品
  5. opengl纹理示例
  6. 如何看待Scrum Sprint Backlog冻结和变化?
  7. 几个常见的Python面试题,帮助大家更加从容面试!
  8. threejs加载模型挤压变形_车用水阀套零件冷挤压成形数值模拟试验研究
  9. Namomo Spring Camp Div2 Week1 - 第五次打卡
  10. The tempotron: a neuron that learns spike timing–based decisions 事件驱动
  11. Flask 源代码阅读笔记
  12. python selenium 获取元素下的元素个数_Python + Selenium,分分钟搭建 Web 自动化框架!(送自动化测试书籍)...
  13. vba 怎么取得一个book中最右边的sheet名_商业观察|探底新零售中的坪效革命
  14. Linux下禁止使用swap及防止OOM机制导致进程被kill掉
  15. (转)知乎-区块链技术:如何赋能供应链创新
  16. 四川大学计算机学院 学术委员会,学术委员会完整列表
  17. HG_REPMGR configure配置
  18. 专访 Livid:程序员值得花时间一看!
  19. 电脑桌面下栏和计算机里面全黑,电脑桌面下面菜单栏变黑条了,为什么?
  20. 快递鸟电⼦⾯单批量打印流程与注意事项

热门文章

  1. Windows Terminal 美化 / PowerShell 美化: oh-my-posh 主题安装和使用
  2. vue中网页图标favicon.ico不显示
  3. 大型网站技术架构·核心原理与案例分析 第八章·固若金汤:网站的安全架构思维导图
  4. 【无标题】star rain
  5. API管理的正确姿势--API Gateway
  6. 低耦合,高内聚的详解(绝对全面)
  7. Linux 学习 十三单元
  8. Maya Python 游戏与影视编程指南 第一章
  9. 常见Mbps、mb、Kbps、kb、bps单位换算-洋葱先生-杨少通
  10. F12开发人员工具如何使用、抓包、调试代码