JNI简明教程

    众所周知,PHP是世界上最好的语言,java排第二,因为PHP无所不能。但是在某些场景下java还要调用本地方法来提高执行的效率,故java只能排第二。java提供了jni(Java Native Interface)来实现在java中调用本地方法。本地方法在java中用native关键字标识,它是一种和机器有关的方法,一般用C或C++实现,而本地方法不是跨平台的,不同的平台需要重新编译。jdk中就有不少地方用了native方法,比如Object类中的hashCode方法:

 

public native int hashCode();

下面开始使用jni。

 

一、创建一个带有native方法的类

package com.example.jni;
public class JNIObject {    private String name;
    public JNIObject(String name) {        this.name = name;    }
    public String getName() {        return this.name;    }        public int add(int param1, int param2) {        return param1 + param2;    }
    public int sub(int param1, int param2) {        return param1 - param2;    }
    public native int multi(int param1, int param2);
    public native int div(int param1, int param2);}

我们假定加减法执行效率高可以直接用java实现,而乘除法比较慢,需要用C语言来实现。写好了类我们先编译,把java文件编译成class文件,然后再用javah命令生成C头文件,执行javah命令时要注意,我们需要先把当前的工作目录切换到class所在的根目录,就是包的第一级目录所在的目录。比如我们的包名是com.example.jni,那么我们需要切换到com目录所在的目录,执行的命令格式是javah [-option] 包名.类名

 

javah -jni com.example.jni.JNIObject

成功后会在当前的工作目录生成一个.h的文件(com_example_jni_JNIObject.h),至此我们就得到了本地方法的接口了,如果有C程序员,可以交给他们实现,否则看第二步。

 

二、在CLion中实现native方法

C语言的开发环境有很多,在windows平台最著名的是微软公司推出的Visual Studio,一旦你体验过了JetBrains公司的产品,分分钟想卸载Visual Studio。所以这里使用CLion来作为C语言的开发环境。

2.1、创建C项目

创建一个C Library项目,填好路径和项目名,Library type选择shared,Language standard是指C语言的不同标准,类似于我们的jdk的版本,如果你不是C程序员,直接用默认的C99标准就好了

2.2、设置编译环境

如果你使用过Visual Studio的话,你可能安装之后直接创建项目就能写代码了,但是CLion有点不同,它只是一个开发集成环境,只提供了构建工具cmake,并没有提供编译器(Visual Studio全套都提供好了),这可以让开发者自由去选择自己喜欢的编译器,我们打开File->Settings,并选中Build,Execution,Deployment下的Toolchains

可以看到CLion列出了两个常用的编译器MinGW和Cygwin,这里我们使用MinGW作为我们的编译器。需要注意的是如果你的jdk是64位的,那么也要选择64位的MinGW,不然在调用的时候会出错,下面是windows下的MinGW64位下载地址

http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download

下载后直接安装就行了,安装成功后在上图中的Environment选择MinGW的home目录,然后下面的C、C++编译器还有调试器都会根据选择的目录自动找到相关工具,然后点OK就完成了我们的编译器的设置。

2.3、实现native方法

我们先把第一步生成的C头文件(com_example_jni_JNIObject.h)复制到CLion项目中,这时在打开的com_example_jni_JNIObject.h文件顶部出现一行提示:This file dose not belong to any project target, code insight features might not work properly. 这时我们打开CMakeLists.txt文件,在add_library中加入我们的头文件,完成后点击提示的reload changes,完成后的CMakeLists.txt的内容如下:

 

cmake_minimum_required(VERSION 3.15)project(jni C)
set(CMAKE_C_STANDARD 99)
add_library(jni SHARED library.c library.h com_example_jni_JNIObject.h)

再切换到com_example_jni_JNIObject.h可以发现刚才的警告已经消失了,但是第二行的#include <jni.h>报错了,这时因为MinGW编译器没有jni.h这个头文件,打开JDK的home目录,在include目录中可以找到jni.h头文件,除此之外,我们还需要include/win32目录下的jni_md.h头文件,一共两个,把这两个头文件都复制到MinGW安装目录(就是设置编译环境时的那个Environment的值)下的x86_64-w64-mingw32中的include目录中,注意这两个头文件是一起放在MinGW的这个目录的,jni_md.h不需要另外创建一个win32目录来存放。完成后发现com_example_jni_JNIObject.h的报错消失了。

我们右键点击项目,选择New->C/C++ Source File,然后创建一个源码文件,type选择.c,如果你习惯使用C++就选.cpp

点击OK完成,下面是具体的实现

​​​​​​​

#include "com_example_jni_JNIObject.h"
JNIEXPORT jint JNICALL Java_com_example_jni_JNIObject_multi        (JNIEnv *env, jobject o, jint param1, jint param2) {    return param1 * param2;}
JNIEXPORT jint JNICALL Java_com_example_jni_JNIObject_div        (JNIEnv *env, jobject o, jint param1, jint param2) {    return param1 / param2;}

解释一下上面的源文件,#include是把后面的com_example_jni_JNIObject.h头文件包含进来,和java的import作用类似。下面的两个方法就是在这个头文件中声明的函数,C语言在声明函数时可以忽略参数名,只写参数类型,但是现在我们是在实现函数,所以必须加上参数名。如果是学习过c语言的很容易理解,没学过的了解一下就好。

2.4、编译动态链接库

到此,我们的代码就完成了,点击菜单栏的Build->Build Project成功后在在左侧的项目结构里生成了一些文件,其中cmake-build-debug目录下的libjni.dll就是我们需要的动态链接库了,如果是linux系统,生成的是.so格式的文件。

 

三、在java中调用native方法

回到java目录,我们在项目的根目录下创建一个jni目录,把我们的dll文件复制进去,复制好之后会自动打开,发现是乱码,因为dll文件是二进制格式的,我们直接关掉。

在JNIObject类中添加一个静态代码块,用来加载我们的动态链接库,完成后的JNIObject类如下:

​​​​​​​

public class JNIObject {
    static {        System.loadLibrary("libjni");        }        private String name;
    public JNIObject(String name) {        this.name = name;    }
    ...}

注意loadLibrary方法不用写dll后缀名。

我们新建一个测试类Main,代码如下:​​​​​​​

package com.example.main;

import com.example.jni.JNIObject;

public class Main {

    public static void main(String[] args) {        JNIObject jniObject = new JNIObject("jni");        System.out.println(jniObject.getName()); // 调用java方法        System.out.println(jniObject.add(1, 2)); // 调用java方法        System.out.println(jniObject.sub(1, 2)); // 调用java方法        System.out.println(jniObject.multi(2, 3)); // 调用native方法        System.out.println(jniObject.div(6, 2)); // 调用native方法    }}

我们先运行一下Main类的main方法,发现报错了:

 

Exception in thread "main" java.lang.UnsatisfiedLinkError: no libjni in java.library.path  at 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.example.jni.JNIObject.<clinit>(JNIObject.java:6)  at com.example.main.Main.main(Main.java:8)

这是因为我们还没有指定jni库的加载路径,导致loadLibrary方法无法找到我们的dll库。点开运行按钮下拉菜单的Edit Configurations,我们给Main类加一个启动参数-Djava.library.path,这个参数就是异常信息出现的参数,指定值为jni目录

重新运行main方法,可以看到已经可以正常执行native方法了。

 

四、总结

总结一下jni的调用过程——先定义好native方法,然后通过javah生成头文件,然后用C或C++实现函数,编译成动态链接库,把动态链接库加入到java项目当中,通过System.loadLibrary(String)方法加载,最后就可以调用了。

值得说明的是,虽说本地方法执行效率高,但并不是说用jni就一定好,除了开发成本的问题,java的JIT技术等也使得java的执行效率越来越高,具体还是要看使用场景来做选择。

 

更多的JAVA后端技术,Spring Cloud等技术专题,请关注以下公众号,大家可以一起探讨。

​​​​​​​

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值