前言
做开发的,喜欢看源码的同学,肯定会遇到native关键词,接着就会卡在这里进行不下去,因为native对应的代码就是C了,如果要看再底层的代码,就之只能上官网要C层面的代码了。
2年前,笔者开发过JNI(Java Native Interface),java和C都要实现,这里拿出了笔者多年前的笔记,加点润色说明(貌似也没润多少),希望对大家有所帮助。
目标:
- 理解native是什么?
- 自己如何实现jni调用?
了解什么是native
我们在学习JVM运行数据区的时候,会了解到虚拟机栈和本地方法栈,我自己做笔记、画图做总结的时候,喜欢把这两个划分到一块,这里可以参考下面的图。
那么JNI是什么?为什么会有native关键词?
JNI(Java Native Interface)调用属于线程私有,用于本地方法的运行,一个Native Method就是一个java调用非java代码的接口,一般来说就是C实现了。
为什么需要Java调用C实现?
有时候Java不能提供的功能(例如类加载机制中,bootstrap的作用是加载加载$JAVA_HOME/jre/lib/下核心类库,如rt.jar,而底层源码hotspot,jvm中是由C++实现),或者是由C来处理更方便更快,那么就需要native方法。
java中bootstrap加载核心类,底层源码可以参考查看: jdk8-hotspot,个人认为,有时候看看底层源码,又能理解出一些新知识,有能力的可以多看看。
正片:一个demo
1.准备好环境
准备好JDK,安装好JDK,配备好环境(这里就不加累述)
2.手打一个JNI的java文件
创建一个TestJNI.java文件:
1)vim TestJNI.java
2)按 i 编写(编写了加载so文件的加载路径,与对应的native方法)
3)按ESC键,:wq! 保存退出
public class TestJNI {
public static native void printTest(String content);
static {
/**这是默认读取的加载路径*/
// System.loadLibrary("TestJNI");
/**指定加载动态库so的路径*/
System.load("/home/aguicode/programmer/java_project/libTestJNI.so");
}
public static void main(String[] args){
String content="Success";
printTest(content);
return;
}
}
3. 编译TestJNI.java文件
javac TestJNI.java # 生成class文件
如果遇到权限不够的问题:
1)su 转换为root权限
4.生成TestJNI.h的头文件
javah TestJNI # 会自动生成C需要的头文件,接着在C实现对应cpp就行啦
PS: 当然也可以自己按照以上规范自己码好.h
头文件。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNI */
#ifndef _Included_TestJNI
#define _Included_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TestJNI
* Method: printTest
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_TestJNI_printTest
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
5. 创建TestJNI.c文件
1)vim TestJNI.c
2)按 i 编写
#include <jni.h>
#include <stdio.h>
#include <TestJNI.h>
JNIEXPORT void JNICALL Java_TestJNI_printTest
(JNIEnv * env, jobject obj, jstring content) {
const jbyte* str=(const jbyte*)(*env)->GetStringUTFChars(env,content,JNI_FALSE);
printf("Hello------->%s\n",str);
(*env)->ReleaseStringUTFChars(env,content,(const char*)str);
return;
}
6.生成os文件(Linux为os,Window为dll)
通过linux带有gcc,进行对C代码的编译。看到第5点中,需要引入jni.h等的头文件,需要 -I
gcc -fPIC -D_REENTRANT -I/usr/jdk/include/linux/ -I/usr/jdk/include -I/home/aguicode/programmer/java_project/ -c TestJNI.c
gcc -shared TestJNI.o -o libTestJNI.so # 两个文件
7. 调用os,实现JNI
1)TestJNI.java中直接调用so,可以之间编译TestJNI就行,或者重新创建一个Test.java,调用TestJNI(),进而调用TestJNI.so;
2)编译生成class文件
3)运行:java TestJNI TestJNI:类名
javac TestJNI.java
java TestJNI
看到hello,success,那么本次demo就完成了,感兴趣的可以试一试。
小结
大概过程是:先有TestJNI.java
(1)文件,接着通过javah生成TestJNI.h
(3)供C来实现TestJNI.c
(4),接着系统编译so文件产生TestJNI.o
(5)与TestJNI.so
(5),接着通过main函数来调用native方法,java编译class文件TestJNI.class
(2)
一些实战技巧
关于多cpp打包问题
以上只是针对一个头文件的实现,但是如果要实现一个企业级的JNI,那么一个so的动态链接库可能会对应很多代码实现,这里就可能不止是一个.h
与一个.cpp
的实现了,可能包含有很多cpp实现,这里就需要使用makefile
来实现动态链接库的生成。
TODO:makefile企业级的使用,JNA,JNR使用~