JNI简介

JNI(Java Native Interface)是JDK的一部分,提供了若干API实现了Java和其他语言的通信(主要是C/C++)。JNI主要用于以下场景:

  • 贴近硬件底层的功能,Java无法实现;
  • 复用已有的程序(非Java开发);
  • 对部分代码有较高的性能要求,如矩阵运算、图形渲染等;

在java的源码中,也多处使用了JNI,例如在Thread.java中,底层用于新建线程的方法,就是通过JNI,使用本地语言实现的。

Java的特点的跨平台、可移植性强。但由于运行在JVM中,相对于本地语言来说,性能方面有一定的劣势。使用JNI可以使Java可以与本地语言互相通信(Java中可以调用本地语言的方法,本地语言也可以调用Java的方法),一定程度上提升代码的性能,但由于本地语言的引入,又让Java失去了平台移植的特性。

使用JNI时,本地语言通常是C/C++,可以看成Java与C/C++的混合开发。需要了解C/C++基本法语言 、头文件、动态链接库、调用约定等概念。

使用JNI的步骤如下:

  1. 编写.java文件,需要使用本地语言实现的方法,添加native关键字,并且不需要实现(不在.java文件中实现)。
  2. 使用javac工具,编译.java文件,生成.class文件。
  3. 使用javah工具,生成.h头文件。
  4. 新建与.h文件对应的.cpp文件,实现.h文件中的函数。
  5. 编译.cpp文件 ,生成动态链接库文件(Windows下为dll文件,Linux下位so文件)
  6. 联合.class文件,使用java工具运行。

如下图所示:

《图片来自百度百科》

第一个JNI程序

下面举一个例子,在Java中,使用C语言的printf()函数,输出"Hello World"。根据上面的流程,一步步执行。

1、编写java文件

新建一个HelloWorld.java文件,文件的内容如下:

public class HelloWorld {

public native void showHelloWorld();

static {

System.out.println(System.getProperty("user.dir"));

System.out.println(System.getProperty("java.library.path"));

System.loadLibrary("HelloWorld");

}

public static void main(String[] args) {

new HelloWorld().showHelloWorld();

}

}

有两点需要注意:

  • showHelloWorld()方法声明中有native关键字,表示该方法使用JNI,使用本地语言实现,在java文件中,不需要实现也能通过编译。
  • 在静态区块中,调用System.loadLibrary()方法,加载一个动态链接库。这个动态链接库等会由我们生成(在Windows下为dll文件,在Linux下为so文件 ),我们只需要指定文件名,JVM会根据当前的操作系统选择合适的文件格式。

此外,静态区块中还输出了两个JVM的系统属性,后面会详细说明这两个属性。

2、使用javac编译

和编译普通的Java程序一样,编译前先新建一个out文件夹,使用-d参数编译。

javac -d ./out HelloWorld.java

如果没有错误,将会在out文件夹下,生成HelloWorld.class文件。

3、使用javah生成头文件

javah工具也是JDK的一部分,专门用于JNI开发,根据.java文件,生成.h文件。

使用方法与javac类似:

javah HelloWorld

注意,不要带有java后缀名。正常的话,会在.java的同级文件夹,生成.h文件。

本例中,生成的HelloWorld.h的内容如下:

jni.h由JDK提供,是JNI的一部分,该文件存放在<JAVA_HOME>/include路径下。

JNIEXPORT和JNICALL都是宏定义,不同平台的定义不同,在Windows下的定义如下:

__declspec(dllexport)表示声明的函数为导出函数。

__stdcall是调用约定的一种,表示函数参数从右至左入栈,调用接受后,函数内部完成堆栈清理。

以上定义可以在jni_md.h文件中找到,该文件位于<JAVA_HOME>/include/win32

注意jni.h与jni_md.h所在的路径,后面编译动态链接库时,会用到

该头文件中声明了一个函数——Java_HelloWorld_showHelloWorld(),该函数就是HelloWorld.java文件中,使用native声明的showHelloWorld()方法。native方法在没有重载的情况下,与本地代码中的函数对应关系如下:

Java_<包名>_<类名>_<方法名>();

4、编写cpp文件,实现头文件中的函数

在HelloWorld.h所在的目录下,新建HelloWorld.cpp文件,文件内容如下:

#include "HelloWorld.h"

#include <stdio.h>

JNIEXPORT void JNICALL Java_HelloWorld_showHelloWorld

(JNIEnv *, jobject)

{

printf("Hello World");

}

注意第一行,包含了javah生成的HelloWorld.h文件,并对头文件中的函数进行了实现。

5、编译cpp文件位动态链接库

不同平台的编译工具五花八门,例如在Window下,可以直接使用cl.exe编译;或编写Makefile,使用nmake编译;又或者编写vcxproj文件,使用msbuild编译。在Linux平台下,可以使用gcc编译,也可以编写Makefile,使用make工具编译。

为了尽可能保证可移植性,这里选择CMake作为编译工具,让本地代码尽可能的支持多平台。CMake的使用说明可以参考《CMake简易入门》。

在HelloWorld.cpp所在的目录下,新建CMakeLists.txt文件,文件内容如下:

# 指定cmake的最低版本

cmake_minimum_required(VERSION 2.8)

# 指定项目名

project(HelloWorld)

# 添加头文件

include_directories($ENV{JAVA_HOME}/include $ENV{JAVA_HOME}/include/win32)

# 设置生成目录

SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR})

# 生成链接库文件,三个参数分别是链接库名、链接库类型、源码文件

add_library(${PROJECT_NAME} SHARED HelloWorld.cpp)

第3个命令,指定头文件jni.h与jni_md.h所在的路径。

第5个命令,表示要编译生成一个动态链接库文件,第一个参数指定动态链接库文件名。PROJECT_NAME是cmake内置的变量,其值有第2个命令配置为HelloWorld。本例中,需要和第1步中,System.loadLibrary()入参相同。

编写结束后,打开VS的命令行窗口。VS的命令行窗口有x86、x64两种,需要和JDK位数对应。JDK的位数,可以使用命令"java -version"查看:

打开VS的命令行后,cd切换到CMakeLists.txt所在的目录,输入以下命令,生成Makefile(本例子使用nmake工具编译):

cmake -G "NMake Makefiles" -B .\out .

成功运行后,在out目录下,会生成以下文件:

之后,使用以下命令(在CMakeLists.txt所在的目录),编译生成dll文件:

cmake --build ./out --target HelloWorld

或者可以使用cd命令进入out文件夹,之后使用namke命令编译:

cd out

nmake

无论是那种方法,编译成功后,均会在out目录下,生成HelloWorld.dll文件(还有其他文件,这里不关心):

6、使用java工具,运行测试

命令行窗口切换到out目录,使用以下java工具,运行HelloWorld.class程序:

java HelloWorld

如果前面步骤没有问题,会有以下输出:

先看末尾,红框中的"Hello World"就是用本地代码——printf()输出的结果,这说明Java与C/C++的交互成功了。

再回到HelloWorld.java中,System.loadLibrary()仅根据一个文件名就找到了动态链接库文件,这里引出一个问题,动态链接库文件要保存在哪里?是不是和本例中,和.class文件保存在同级目录就可以了?

答案是否定的。System.loadLibrary()加载动态连接库时,会到指定的路径中的搜索,这个路径保存在JVM系统参数java.library.path中。该参数实际上就是本地系统的环境变量PATH加上当前路径(注意该参数的末尾,";."表示当前路径,也就是JVM系统参数user.dir的值)。如果在这些路径中找不到指定的动态链接库,就会抛出以下java.lang.UnsatisfiedLinkError:

使用IDE

但工程毕竟复制时,命令行编译就会变得毕竟繁琐。下面使用idea+VS的组合,完成JNI的开发。idea复制java部分的编译,VS负责C++的编译。(据说VS 2019支持Java ,那个时候估计只需要VS就可以了)

同样按照上述的步骤。

  1. idea新建Java工程

    新建一个空的Java工程,命名为JNI_Demo。

    在src目录下,新建一个HelloWorld.java,文件内容和之前相同。

  2. 编译Java工程

    点击运行按钮,编译并允许,会有以下输出:

    抛出异常是正常的,我们还没有创建动态链接库文件,自然找不到。

    运行的目的是查看JVM系统参数user.dir的值,动态链接库生成后要保存在这个路径。

  3. 使用javah工具生成头文件

    右键工程视图中的HelloWorld.java文件,选择"Open in Terminal",在打开的命令行窗口使用javah工具,生成HelloWorld.h文件。

    成功后,可以在工程视图中看到HelloWorld.h文件。

  4. 使用VS新建动态链接库工程

    右键工程视图中的HelloWorld.h文件,选择"Show in Explorer",查看该文件的保存位置。

    使用VS,在该位置创建一个动态链接库工程,同样命名为HelloWorld:

    右键工程视图的"Header Files"文件夹,选择add-Existing Item,添加HelloWorld.h文件到工程中。

    编写工程中的HelloWorld.cpp文件,内容与之前的类似。

  5. 编译动态链接库

    编译前,需要对工程进行配置 。

    首先选择动态库生成的位数,与JDK要相同。

    工程视图中,右键HelloWorld工程,选择"Properties"(或者使用Alt + F7),打开工程属性配置窗口。

    先配置动态库文件生成的位置,目标位置就是第1步中,user.dir的值(也就是java工程的根目录)。

    之后添加头文件路径,一共需要添加3个路径:

    这3个路径分别是jni.h、jni_md.h、HelloWorld.h这3个头文件所在的目录。

    配置正确,HelloWorld.cpp中的下滑红线会全部消失。右键项目,选择"Build",开始编译。

    编译成功的话,在idea的工程视图,可以看到HelloWorld.dll文件:

  6. 运行测试

    回到idea,工程视图中右键src/HelloWorld文件夹,选择"Mark Directory as"-"Exclusion"。不然运行程序会有以下错误:

    配置结束后,就可以正常运行程序:

除了idea + VS的组合外,还有其他多种组合,没有固定搭配,选择合适自己的工具即可。

进阶

结束了吗?不,到这里为止才刚刚开始。通过JNI我们可以与本地代码相互通信,互相通信包括:

  • Java调用本地代码
  • 本地代码调用Java方法
  • Java向本地代码传递参数
  • 本地代码项Java返回数据

我们目前只完成了第一步,后面的可以参考官方文档:

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html

中文翻译可以百度"JNI-API完全手册"。

JNI简易入门的更多相关文章

  1. 机器学习简易入门(四)- logistic回归

    摘要:使用logistic回归来预测某个人的入学申请是否会被接受 声明:(本文的内容非原创,但经过本人翻译和总结而来,转载请注明出处) 本文内容来源:https://www.dataquest.io/ ...

  2. Pandas简易入门(二)

    目录:     处理缺失数据     制作透视图     删除含空数据的行和列     多行索引     使用apply函数   本节主要介绍如何处理缺失的数据,可以参考原文:https://www. ...

  3. Android Studio JNI开发入门教程

    Android Studio JNI开发入门教程 2016-08-29 14:38 3269人阅读 评论(0) 收藏 举报  分类: JNI(3)    目录(?)[+]   概述 在Andorid ...

  4. 不用搭环境的10分钟AngularJS指令简易入门01(含例子)

    不用搭环境的10分钟AngularJS指令简易入门01(含例子) `#不用搭环境系列AngularJS教程01,前端新手也可以轻松入坑~阅读本文大概需要10分钟~` AngularJS的指令是一大特色 ...

  5. Web压力测试工具 LoadRunner12.x简易入门教程--(一)回放与录制

        LoadRunner12.x简易入门教程--(一)回放与录制 今天在这里分享一下LoadRunner12.x版本的入门使用方法,希望对刚接触LoadRunner的童鞋有所帮助. LoadRun ...

  6. Android M中 JNI的入门学习

    今年谷歌推出了Android 6.0,作为安卓开发人员,对其学习掌握肯定是必不可少的,今天小编和大家分享的就是Android 6.0中的 JNI相关知识,这是在一个安卓教程网上看到的内容,感觉很不错, ...

  7. crontab简易入门

    前言 crontab是Unix和Linux用于设置周期性被执行的指令,是互联网很常用的技术,很多任务都会设置在crontab循环执行,如果不使用crontab,那么任务就是常驻程序,这对你的程序要求比 ...

  8. Golang项目的配置管理——Viper简易入门配置

    Golang项目的配置管理--Viper简易入门配置 What is Viper? From:https://github.com/spf13/viper Viper is a complete co ...

  9. MyCAT简易入门

    MyCAT是mysql中间件,前身是阿里大名鼎鼎的Cobar,Cobar在开源了一段时间后,不了了之.于是MyCAT扛起了这面大旗,在大数据时代,其重要性愈发彰显.这篇文章主要是MyCAT的入门部署. ...

随机推荐

  1. C语言基础知识【运算符】

    C 运算符1.运算符是一种告诉编译器执行特定的数学或逻辑操作的符号.C 语言内置了丰富的运算符,并提供了以下类型的运算符:算术运算符关系运算符逻辑运算符位运算符赋值运算符杂项运算符2.杂项运算符 ↦ ...

  2. centos7.0 增加/usr分区的容量减少home分区的大小

    把/home内容备份,然后将/home文件系统所在的逻辑卷删除,扩大/root文件系统,新建/home:tar cvf /tmp/home.tar /home #备份/homeumount /home ...

  3. Web前端开发规范【HTML/JavaScript/CSS】

    前言 这是一份旨在增强团队的开发协作,提高代码质量和打造开发基石的编码风格规范,其中包含了 HTML, JavaScript 和 CSS/SCSS 这几个部分.我们知道,当一个团队开始指定并实行编码规 ...

  4. 大华NVR设备接分别入宇视摄像机Onvif和RTSP主子码流的方案说明

    需求提要 1.各个内网现场有多种网络摄像机IPC和网络硬盘录像机NVR设备: 2.需要将这些设备统一接入到云端中心平台,进行统一的视频直播和录像回放管理: 3.由于目前IPC设备都属于高清设备,主码流 ...

  5. C#彻底解决Oledb连接Excel数据类型不统一的问题

    在使用Microsoft.Jet.OLEDB.4.0连接Excel,进行读取数据,相对使用传统的COM来读取数据,效率是很高的.但相对传统COM操作Excel来说,及存在数据类型转换的问题.因为使用O ...

  6. POJ 1694 An Old Stone Game【递归+排序】

    链接: http://poj.org/problem?id=1694 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=27454#probl ...

  7. ubuntu问题: 同时只能有一个软件管理工具在运行

    或者是: 只能同时运行一个更新管理器 打开终端输入命令:sudo dpkg –configure -a 运行,系统问题就解决了

  8. MySQL删除相同前缀的表,修改某个库的存储引擎

    MySQL5.0 之后,提供了一个新的数据库information_schema,用来记录MySQL总的元数据信息.元数据指的是 数据的数据. 比如表名.列名.列类型.索引名等表的各种属性名称.这个库 ...

  9. $.ajax()方法详解(转)

    转: http://www.cnblogs.com/tylerdonet/p/3520862.html 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: ...

  10. POJ - 2195 Going Home 【KM】

    题目链接 http://poj.org/problem?id=2195 题意 在一张N * M 的地图上 有 K 个人 和 K 个房子 地图上每个点都是认为可行走的 求 将每个人都分配到不同的房子 求 ...