文章目录

  • 原理
    • 为什么需要JNI
    • 使用JNI的问题
    • JNI调用过程
  • 实现
    • 环境
    • 1. Java native本地方法定义
    • 2. 生成native方法头文件
    • 3. 创建具体CPP文件
    • 4. 将CPP文件编译为动态链接库
    • 4. 测试
  • 总结

原理

Java本地方法(Native Method)通过JNI(Java Native Interface)提供的一系列API调用其他语言的函数实现的相对底层的功能。

为什么需要JNI

  1. 当需要实现的功能依赖操作系统底层的特性,单纯依靠Java无法独立完成,需要借助其他语言;

    保证Java在跨平台的同时实现对底层的控制;

  2. 在Java中直接调用其他语言已经实现的现成功能;

  3. 使用更加底层的语言实现程序对时间敏感性和性能的要求。

使用JNI的问题

  1. 当在某个操作系统下使用了JNI标准,将本地代码编译生成了动态链接库后,如果要将这个程序移植到其他操作系统,需要在新的平台重新编译代码生成动态链接库,根本原因是** JNI依赖动态链接库,但是动态链接库并不跨平台 **;
  2. 对其他语言的不正确使用可能会造成程序出现错误,例如使用C语言进行内存操作时未及时回收内存可能引起的内存泄漏;
  3. 对其他语言的依赖过高,提高了Java和其他语言的耦合性,也提高了对项目代码的维护成本。

JNI调用过程

Java通过JNI调用C/C++编译链接后的动态链接库来实现相对底层的功能。

但是C/C++或其他语言编译链接生成动态链接库又是依赖具体操作系统平台的,因此每种平台都有自己的一套动态链接库。而每个平台都有自己的JVM,不同平台下的JVM,会去加载某个固定类型的动态链接库文件,使得依赖于操作系统的功能可以被正常的调用,JVM才能正常跨平台,这一过程可以参考下面的图来进行理解:

实现

环境

  1. Mac OS 10.15.7;
  2. JDK8

1. Java native本地方法定义

package com.wkw.study.jni;public class JNITest {static{//从 java.library.path 路径上加载动态链接库System.loadLibrary("MyNativeDll");}//定义native方法public static native void callCPPMethod();public static void main(String[] args) {//输出 java.library.path 具体路径System.out.println("DLL path: " + System.getProperty("java.library.path"));//调用动态链接库中的具体方法callCPPMethod();}}

代码主要完成的工作:

  1. 在静态代码块中,调用loadLibrary方法加载本地java.library.path系统变量定义下的动态链接库,参数为不包含扩展名的动态链接库库文件名。在windows平台下会加载dll文件,在Linux平台下会加载so文件,Mac Os下会加载libXxx.jnilib文件,在本例中,会加载libMyNativeDll.jnilib动态链接库库文件
  2. 声明了一个native方法,native关键字负责通知JVM这里调用方法的是本地方法,该方法在外部用其他语言被定义
  3. 在main方法中,打印加载目录的路径,并调用本地方法。

2. 生成native方法头文件

JDK命令生成头文件:

javac -h ./ JNITest.java #-h指定生成头文件的目录

该命名会生成两个文件:首先生成JNITest.class,其次在指定目录下生成C/C++头文件com_wkw_study_jni_JNITest.h

头文件com_wkw_study_jni_JNITest.h定义了一个方法Java_com_wkw_study_jni_JNITest_callCPPMethod,这个方法对应定义的Java native方法public static native void callCPPMethod()
JNI在C/C++语言中定义的规则:Java_包名_类名_方法名,头文件命名规则:包名_类名.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_wkw_study_jni_JNITest */#ifndef _Included_com_wkw_study_jni_JNITest
#define _Included_com_wkw_study_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/** Class:     com_wkw_study_jni_JNITest* Method:    callCPPMethod* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_wkw_study_jni_JNITest_callCPPMethod(JNIEnv *, jclass);#ifdef __cplusplus
}
#endif
#endif

可以看到头文件中只有函数声明,没有具体实现:

  1. extern "C"表示这部分代码使用C语言规则来进行编译和连接的;
  2. JNIEXPORTJNICALL是头文件jni.h中定义的两个宏,使用JNIEXPORT支持在外部程序代码中调用该动态库中的方法,使用JNICALL定义函数调用时参数的入栈出栈约定;
  3. 函数名称由Java前缀+包名+类名+方法名组成,在该方法中有两个参数,通过第一个参数JNIEnv *的对象可以调用jni.h中封装好的大量函数 ,第二个参数代表着native方法的调用者,当Java代码中定义的native方法是静态方法时这里的参数是jclass,非静态方法的参数是jobject。

3. 创建具体CPP文件

#include "com_wkw_study_jni_JNITest.h"
#include <stdio.h>JNIEXPORT void JNICALL Java_com_wkw_study_jni_JNITest_callCPPMethod (JNIEnv *, jclass) {printf("**CPP Method**\nprint from cpp");
}

引用头文件并实现其中的函数,也就是native方法将要实际执行的逻辑,CPP文件名随意,本例中为callCPPMethod.cpp

4. 将CPP文件编译为动态链接库

gcc -I"./" -I"/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/include" -I"/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/include/darwin" -shared -o libMyNativeDll.jnilib callCPPMethod.cpp

这里使用gcc编译器进行编译:

  1. -I指定头文件的路径,本例中需要3个头文件jni.hjni_md.hcom_wkw_study_jni_JNITest.h,所以引入了3次:

    com_wkw_study_jni_JNITest.h在当前目录下;

jni.h/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/include目录下;
jni_md.h/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/include/darwin目录下;
2. -shared指定生成动态链接库,如果不使用这个标志那么外部程序将无法连接;
3. -o指定目标的名称,这里将生成的动态链接库命名为libMyNativeDll.jnilib
4. callCPPMethod.cpp为被编译的C/C++源程序文件名。

gcc命令具体过程:

4. 测试

当前已经完成自定义一个Java native方法的所有流程,来看下生成的所有文件:

接下来测试验证跑下JNITest.main方法,异常:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no MyNativeDll in java.library.pathat java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)at java.lang.Runtime.loadLibrary0(Runtime.java:870)at java.lang.System.loadLibrary(System.java:1122)at com.wkw.study.jni.JNITest.<clinit>(JNITest.java:6)

找不到动态链接库MyNativeDll。这是因为System.loadLibrary("MyNativeDll")是从系统变量java.library.path指定的路径中找libMyNativeDll.jnilib动态链接库文件并进行加载,因此需要将libMyNativeDll.jnilib放到java.library.path指定的路径下,有两种方法:

  1. 修改JVM参数,直接将java.library.path的路径指定为当前libMyNativeDll.jnilib库文件所在的路径:

    -Djava.library.path=/Users/developmac/workspace/develop/ideaWorkspace/comprehensive-learning/web-study/src/main/java/com/wkw/study/jni/
    
  2. libMyNativeDll.jnilib库文件拷贝到默认的加载目录下,具体的路径可以通过System.getProperty("java.library.path")获取,该方法可能会获得多个目录,放在任意一个目录下即可。

再次启动,运行符合预期:

DLL path: /Users/developmac/workspace/develop/ideaWorkspace/comprehensive-learning/web-study/src/main/java/com/wkw/study/jni/
**CPP Method**
print from cpp

总结

JNI调用过程:

从代理模式角度来看:

  • 代理角色:包含native方法的JNI类

  • 实现角色:C/C++或其他语言实现的动态链接库

  • 客户端:调用native方法的java类程序

  • 接口(抽象角色):在JNI中接口这一角色的存在感相对薄弱,因为JNI是跨语言的,所以说无法严格的定义一个接口并让它同时应用于Java和其他语言。但是通过生成的.h头文件,在一定程度上实现了从接口规范上统一了Java中native方法和其他语言中的函数。

    所以上图中让客户端的调用过程跳过了接口,直接指向了代理角色,再由代理角色调用实现角色完成功能的调用。

总的来说,JNI起到了一个代理或中介的作用,与常见代理不同的是这里只做方法的调用,而不实现逻辑上的增强。

参考:

Java筑基 - JNI到底是个啥

Mac OS上编译JNI的动态库

Java-JNI调用过程相关推荐

  1. Java JNI调用IC卡读卡器

    Java JNI调用IC卡读卡器 Java调用IC卡读卡器的DLL一般有以下2种方式: 1.使用JNative第三方库调用IC卡读卡器dll的接口. 2.使用JNI调用IC卡读卡器dll的接口. JN ...

  2. java jni调用dll文件_Java通过jni调用动态链接库

    (1)JNI简介 JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++).从Java1.1开始,JNI标准成为java ...

  3. JAVA JNI调用科大讯飞离线语音合成(Linux篇)

    JNI调用科大讯飞离线语音合成(Linux篇) 背景:之前已经介绍过JNI调用科大讯飞离线语音合成(Windows篇),今天我们接着聊一聊在Linux系统中如何使用科大讯飞离线语音合成,我们这里以(u ...

  4. JAVA方法调用过程(最详细的解释)

    弄清楚方法的调用过程,对于我们学习java极为重要,下面是调用过程的详细描述: 1.编译器查看对象的声明类型和方法名. 编译器去寻找所有名字为f但参数类型不同的方法.例如可能存在方法f(int)和方法 ...

  5. java JNI调用C++代码(给出一个简单java application示例和实际java web项目过程及错误解决)(一)

    一.简单java application示例篇 转载请注明:https://blog.csdn.net/xitie8523/article/details/79926948 本科毕业设计是图像分割相关 ...

  6. java JNI调用C++代码(给出一个简单java application示例和实际java web项目过程及错误解决)(二)

    二.java web 服务器(tomcat)调用图像处理C++代码项目实例 转载请注明:https://blog.csdn.net/xitie8523/article/details/80009821 ...

  7. android JNI调用过程中的方法签名规则

    有了数据类型之间的对应关系,JNI就可以正确识别并转换Java类型,但是Java支持方法重载,仅靠函数名是无法唯一确定一个方法的.于是JNI提供了一套签名规则,用一个字符串来唯一确定一个方法.其规则如 ...

  8. java JNI调用C语言动态链接库(java.lang.UnsatisfiedLinkError: no yourClassName in java.library.path 异常的解决方法)

    转自: http://watershitter.iteye.com/blog/477615 今天花了至少3个小时跑这个java调c的动态链接库的Hello,native world的程序.  把所有犯 ...

  9. java jni调用dll_浅谈JNI的使用--java调用dll(原创)

    什么是JNI?Java Native Interface(Java本地接口)的简写.使用这个接口,可以轻松实现java对动态链接库Dynamic Link Library(dll)文件的调用,以实现一 ...

  10. Java JNI调用C语言中的函数

    1.调用无参函数 java code public class JNITest {static {/*加载*/System.loadLibrary("mynative");}pub ...

最新文章

  1. mysql存储引擎静态表_MySQL存储引擎(表类型)的选择
  2. matlab通信物理层仿真,通信小精灵(物理层仿真工具) 可计算仿真误码率、理论误...
  3. php.ini 延迟,php超时报错Maximum execution time of 120 seconds exceeded in解决办法
  4. 安装虚拟机Ubuntu,搭建lnmp环境碰到的坑(二)
  5. Elasticsearch 7.0 已经发布,盘他!
  6. NIPS 2016 | Best Paper, Dual Learning, Review Network, VQA 等论文选介
  7. 如何更改html广告,js 动态改变广告代码DIV的位置_原生JS通过innerHTML改变div位置...
  8. 如何用新安装的jdk替换掉Linux系统默认jdk
  9. 与非门如何变成非门,与门,异或门
  10. 【题解】P3939数颜色
  11. 支付宝企业付款PHP版本(转账到支付宝账户)
  12. 手机系统更新(提示已是最新版本),怎么升级更新
  13. Bowe BSB 1005/B
  14. 2021引领量子计算研究热潮的18大研究机构
  15. next在java什么意思_Java中,一个类里面的关键字 next 是什么意思
  16. 职业教育的春天 ——职业教育系列行研报告(一)
  17. 历年全国Java二级考试_历年计算机等级二级考试Java真题(文字版)
  18. 粉碎机无法粉碎文件_5粉碎敏捷挑战以及如何克服它们
  19. NMOS和PMOS作为电源开关经典场景
  20. 【调剂】3.20计算机考研其余调剂信息

热门文章

  1. LTE基本结构(常见接口)
  2. 学会QT从这里开始——教你快速学会QT
  3. 记一次wwwscan目录扫描后获取敏感目录登录后台
  4. JSzip 前端处理下载打包文件夹
  5. JQuery UI Layout
  6. 剪枝算法(算法优化)
  7. JAVA实现List集合去重
  8. pid c语言算法plc,三种比较简单pid控制算法公式
  9. gcode2pointClound
  10. 计算机cad模板样例,教你如何新建适合自己的CAD模板