欢迎浏览我的博客 获得更多精彩文章
https://boyn.top
JNI教程(二):初探JNI
JNI开发流程
- 编写Java程序,声明好要使用native的方法
- 编译Java程序
- 创建C/C++头文件
- 编写C/C++程序
- 创建链接库
- 运行Java程序
下面将分步骤来演示一个简单的JNI开发过程
1.编写Java程序
本地方法在Java中是没有实现的方法体的,我们需要先声明这个方法,才能使用,native方法在声明结束后直接用一个分号结尾,下面是一个实例
编写一个Test.java
1 public class Test{
2 public native void hello();
3 }
编写一个Main.java
public class Main{
public static void main(String[] args){
Test test = new Test();
test.hello();
}
}
本地方法可以声明为public,private,protected, 也可以是static 或者非static 的,一个类文件中,可以有任意多个native方法
需要注意的是,你不能将native方法声明成abstract方法,同时,native关键字只能够用于方法,不能用于变量的声明和类的声明.
下面我们来写一个以后要用的到例子
package top.boyn;
public class HelloJNI{
static{
System.loadLibrary("beginningJni");
}
public native void hello();
public static void main(String[] args){
HelloJNI hello = new HelloJNI();
hello.hello();
}
}
HelloJNI.java表明了:
-
native方法是在运行时进行动态库链接的,而不是编译时,所以我们在写好后,可以将这个java文件编译成字节码,在运行时才要指定动态库
加载动态链接库
假设我们已经写好了一个动态链接库(beginningJava.dll),我们可以以这样的方式来将这个库加载到JVM中
System.loadLibrary("beginningJava"); //第一种 Runtime.getRuntime().loadLibrary("beginningJava"); //第二种
注意,在加载这个库时,不需要写出链接库文件的后缀名,我们在Java程序中只需要给出文件名,在做一些跨平台程序时,直接替换dll文件,而不用更改java代码.
那么,loadLibrary()方法是怎么知道去加载这些库的呢?我们可以用两种方法让JVM去知道这些链接库的位置
- 将库文件放在系统变量: PATH(Windows平台)\LD_LIBRARY_PATH(UNIX平台)的路径下
- 在运行JVM时,加参数指定加载库文件的路径
java -Djava.library.path="Your library path" your-class-name
2.编译java程序
编译声明了native方法的类跟编译普通的类流程一模一样,所以这一部分没有什么好说的,直接用javac来编译就好了
javac HelloJNI.java
3.创建C/C++头文件
在你开始用C/C++实现这些native方法之前,我们需要创造一个包含方法声明的头文件.幸好的是,java已经提供了一个生成头文件的工具给我们,名为javah.所以我们就用这个工具来生成头文件吧
在项目的主目录下,我们运行
javah top.boyn.HelloJNI
就可以看到在项目的主目录下出现了一个top_boyn_HelloJNI.h的头文件,命名的规则是将类前缀的点(.)改为下划线,将类名全部大写,你可以设定-o参数来指定输出的名字,不过还是推荐用默认的规则来生成头文件
如果运行的时候提示找不到类文件,我们就要用-cp参数指定我们项目的主目录
javah -cp YOUR_PROJECT_DIRECTORY top.boyn.HelloJNI
你不需要去关心这些头文件是如何组成的,唯一需要关心的就是,我们在java中声明的native方法在头文件中的样子
JNIEXPORT void JNICALL Java_top_boyn_HelloJNI_hello
(JNIEnv *, jobject);
JNIEXPORT 和 JNICALL是两个宏,void表明这个方法不返回任何值.
在Java方法声明中,我们没有定义参数,但是在本地方法中有一个JNIEnv的指针和一个jobject变量,将这个当做一个规则就好
JNIEnv是一个指针,指向包含了Java环境和native环境之间的函数关系表的对象.第二个参数是jobject,如果我们把这个方法声明为static的话,那这个就是jclass,这个参数就和c++里面的this类似
4.编写C/C++程序
编写对应的C++程序 helloJNI.cpp
#include <stdio.h>
#include <jni.h>
#include "top_boyn_HelloJNI.h"
JNIEXPORT void JNICALL Java_top_boyn_HelloJNI_hello(JNIEnv *env, jobject obj){
printf("Hello JNI\n");
return;
}
在这个程序中,include了三个文件 stdio.h 是用作STDIO演示的, jni.h 用于引入跟JNI有关的函数实现,第三个头文件是我们在上一节中定义的.
为了要编译这个程序,我们要在编译的时候指定jni.h文件的位置,这个文件跟你安装的位置有关,一般要引入两个路径:
- JDK_HOME\include
- JDK_HOME\include\win32(或你的平台名字)
5.创建动态链接库
在这一节中,我们要将这个文件编译成动态链接库文件
关于如何安装c++编译器,网上已经有太多的教程了,在这里就不再赘述
以linux为例,我会演示如何将cpp文件编译成so文件
g++ -shared -I /usr/local/jdk1.8.0_202/include -I /usr/local/jdk1.8.0_202/include/linux -o beginningJava.so helloJNI.cpp
6.运行Java程序
我们在第二节中,已经编译好了Java的类文件,我们只需指定动态链接库的位置,就可以运行这个java程序了
java -Djava.library.path=/home/boyn/Code/Java/JNI/ top.boyn.HelloJNI
Hello JNI
特别要注意的是,如果我们是在linux下运行的话,.so文件要加上lib前缀,才能够被linux系统识别到