首先编写一个简单的类,其中包含一个native方法
public class JNI{
static{
// 加载动态链接库,该动态链接库我们将在下面进行创建
System.loadLibrary("hello_jni");
}
public native void hello();
public static void main(String[] args){
JNI jni = new JNI();
jni.hello();
}
}
我们现在来生成对应的动态链接库,注意我们这里使用的是linux,Linux下的动态库名以.so 或 .so.y结尾,其中y代表版本号,而Windows下为.dll。
编译Java文件并生成java头文件
# 编译出class文件
javac JNI.java
# 生成native方法对应的头文件,头文件名默认以类名开头,比如这里生成的头文件文件名就是 JNI.h
javah JNI -o
生成的头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNI */
#ifndef _Included_JNI
#define _Included_JNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNI
* Method: hello
* Signature: ()V
*/
/**
* 这个函数声明就是对应到我们在java文件中写的native方法
* 我们需要来实现该函数声明,如果我们上面的java文件中存在多个native方法,这里就会出现多个函数声明
* 函数名是固定格式:Java_类全限定名_方法名
*
*/
JNIEXPORT void JNICALL Java_JNI_hello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
JNIEnv指针
JNIEnv,指代了Java本地接口环境(Java Native Interface Environment),是一个JNI接口指针,指向了本地方法的一个函数表,该函数表中的每一个成员指向了一个JNI函数,本地方法通过JNI函数来访问JVM中的数据结构,详情如下图:
JNIEnv指针在<jni.h>文件中的具体实现是一个包含诸多JNI函数的结构体,局部摘要如下:
/*
* We use inlined functions for C++ so that programmers can write:
*
* env->FindClass("java/lang/String")
*
* in C++ rather than:
*
* (*env)->FindClass(env, "java/lang/String")
*
* in C.
*/
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
jint GetVersion() {
return functions->GetVersion(this);
}
jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
jsize len) {
return functions->DefineClass(this, name, loader, buf, len);
}
...
jclass GetObjectClass(jobject obj) {
return functions->GetObjectClass(this,obj);
}
jboolean IsInstanceOf(jobject obj, jclass clazz) {
return functions->IsInstanceOf(this,obj,clazz);
}
jmethodID GetMethodID(jclass clazz, const char *name,
const char *sig) {
return functions->GetMethodID(this,clazz,name,sig);
}
...
}
jobject与jclass类型
jobject与jclass通常作为JNI函数的第二个参数,当所声明Native方法是静态方法时,对应参数jclass,因为静态方法不依赖对象实例,而依赖于类,所以参数中传递的是一个jclass类型。相反,如果声明的Native方法时非静态方法时,那么对应参数是jobject 。
新建一个jni.c文件来实现上面的头文件
#include<stdio.h>
// 引入上面的生成的头文件
#include<JNI.h>
/**
* 实现头文件声明的函数
*/
JNIEXPORT void JNICALL Java_JNI_hello
(JNIEnv * env, jobject obj){
printf("hello jni\n");
}
生成动态链接库文件应该为:libhello_jni.so,这里的名字要和我们上面加载的动态链接库的名字对应上。lib_.so,中间下划线的位置就是我们一个动态链接库的实际名称。lib_.so是一个规范化的名称格式,注意这里使用的是linux
这些命令参数都是GCC的参数,需要去了解一下
# -I是指定头文件的路径 -o是指定输出文件名
# -fPIC是告诉编译器产生与位置无关代码(Position-Independent Code)
# -shared就是生成动态链接库
gcc -fPIC -shared jni.c -I $JAVA_HOME/include -I ./ -I $JAVA_HOME/include/linux -o libhello_jni.so
最终我们就得到了一个名为libhello_jni.so的动态链接库
这时候我们就可以执行一下试试看了
出错了,为什么会出现这样的错误,错误提示非常的清楚,就是动态链接库没有找到,想想也对,凭什么我们直接System.loadLibrary("")就能找到呢?其加载动态链接库是有其自己的规则的,我们上面生成动态链接库的路径就是当前JNI.class所在路径,也就是我们运行java JNI命令时所在路径。
上面出现了java.library.path,其加载动态链接库就是到java.library.path属性对应的路径去查找对应的动态链接库,那怎么去设置路径呢?
1.启动JVM设置参数
java -Djava.library.path=路径 JNI
2.在/etc/profile 后面加上一行 export LB_LIBRARY_PATH=路径
这样就能成功的加载到动态链接库了