Java-JNI简单教程
本文最后更新于:2018年11月20日 晚上
前言
最近决定入坑 Android Framework 了,这样的话,JNI 就一定是需要了解和会使用的,毕竟 Android Framework 就是各种 JNI 调用 c 和 c++ 的驱动然后提供给上层的 Android APP
JNI
JNI 就是 Java Native Interface 的缩写,Java 通过 JNI 来调用 C 的程序和库,这些动态库在 windows 上面以 .dll
的形式存在,在 Unix 世界里以 .so
的形式存在,Android 基于 Linux ,所以实际上 Android Framework 当中调用的各种驱动就是 .so,我在 Linux 上面,尝试使用 Java 调用 c 写的一个方法,输出 Hello World!
Java 的准备
Java 这边我们先要声明一个 native 方法,具体的代码如下
public class HelloWorld {
static {
// 这里导入 hello.dll(windows 下面) 或者 libhello.so (Unixes) 下面
System.loadLibrary("HelloWorld");
}
// 声明一个 native 方法
private native void sayHello();
public static void main(String[] args) {
// 调用 native 方法
new HelloWorld().sayHello();
}
}
生成 Native 的头文件
Native 的头文件,类似于 Java 的接口规范(大概理解,尚不清楚这样到底对不对),java 提供了一个 javah 的工具,帮助我们直接生成 native 头文件 .h
这样的文件,但是使用 javah 之前,要先将 .java
文件编译成 .class
文件,因为 javah 的话是对着 class
文件进行的
所以我们先使用
javac HelloWorld.java
然后使用 javah
${JAVA_HOME}/bin/javah -jni -classpath ${ClassOutputPath} -d ./jni com.ericwyn.jni.HelloWorld
其中 ${JAVA_HOME}
是你 Java 的目录。 ${ClassOutputPath}
是你 Class 文件的输出所在目录,这个在 Intellij 的话一般是 out
目录下
然后一般就会生成一个 com_ericwyn_jni_HelloWorld.h
的文件,因为我的类的名字是com.ericwyn.jni.HelloWorld
,头文件的命名就是将这个类的全名当中的点全部变为下划线这样
这个 .h
文件具体代码如下
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ericwyn_jni_HelloWorld */
#ifndef _Included_com_ericwyn_jni_HelloWorld
#define _Included_com_ericwyn_jni_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_ericwyn_jni_HelloWorld
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_ericwyn_jni_HelloWorld_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
里面就是规定了一个 JNIEXPORT void JNICALL Java_com_ericwyn_jni_HelloWorld_sayHello (JNIEnv *, jobject);
调用头文件编写 c 代码
我们编写一个 c 语言文件,具体代码如下
#include<jni.h>
#include <stdio.h>
#include "com_ericwyn_jni_HelloWorld.h"
JNIEXPORT void JNICALL Java_com_ericwyn_jni_HelloWorld_sayHello(JNIEnv *env, jobject thisObj)
{
printf("Hello World!\n");
return ;
}
头部引入了 jni.h
,另外再引入我们之前生成的头文件
编译 c 代码成为 so
使用下面的代码来将 c 语言文件编译成 .so 库
gcc -fPIC -I "/usr/lib/jvm/java-8-oracle/include/" -I "/usr/lib/jvm/java-8-oracle/include/linux/" -shared -o ./lib/libHelloWorld.so ./jni/HelloWorld.c
其中
-fPIC
是产生位置无关代码,就是可以不固定的内存位置执行代码(就是可以动态链接的意思了),动态链接库必须添加。-I
引入了 Java 的jni.h
和 不同平台下面的一些头文件-share
代表这是一个编译成动态链接库而非可执行文件-o
设置了输出的文件位置
运行
首先我们要先在 Intellij 里面设置运行时候,native 库的位置,其实就是运行 java 的时候增加一个 -Djava.library.path=
,设置为你的 .so 文件所在的文件夹路径
直接设置 Intellij 的 Run Configurations ,在 VM options 里面添加
-Djava.library.path=/work/JNITest/lib
然后运行就可以了
后记
写 JNI 的时候,特别是编译 c 语言文件的时候,真的感受到了作为初级 Java 程序员的我,对编译链的无知。毕竟 Java 平台无关,对开发者屏蔽了平台差异,这样确确实实便利了开发,但是对我们这些刚刚踏入计算机领域的人来说,又是福兮祸兮呢?