2018-6-21 JNI
[TOC]
1. Hello World
在Linux下使用yum安装openjdk
yum install -y java-1.8.0-openjdk-devel gcc
安装完成后就可以使用javac命令编译java文件。
public class TestJni
{
//声明原生函数:参数为String类型
public native void print(String content);
//加载本地库代码
static
{
System.loadLibrary("TestJni");
}
}
编译
javac ./*.java -d .
在TestJni.class同级的目录中运行下面命令生成对应的.h文件
javah -jni TestJni
运行结束后在当前目录中可以看到生成的TestJni.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: print
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_TestJni_print
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
Java_TestJni_print 就对应着TestJni.java 的print方法。
按照c语言的开发思路,对应的需要创建一个TestJni.c文件,实现.h文件中函数
#include <jni.h>
#include <stdio.h>
#include <TestJni.h>
JNIEXPORT void JNICALL
Java_TestJni_print(JNIEnv *env,jobject obj, jstring content){
// 从 instring 字符串取得指向字符串 UTF 编码的指针
// 注意C语言必须(*env)->
const jbyte *str = (const jbyte *)(*env)->GetStringUTFChars(env,content, JNI_FALSE);
printf("Hello --> %s\n",str);
// 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
(*env)->ReleaseStringUTFChars(env, content, (const char *)str);
return;
}
-
JNIEnv使得我们可以使用Java的方法 -
jobject指向在此Java代码中实例化的Java对象LocalFunction的一个句柄,相当于this指针。 -
jstring参数类型对应java中的String。每一个Java里的类型这里有对应的与之匹配。
编译连接生成动态链接库.so文件,得先安装gcc(yum install -y gcc)
cc -I/usr/lib/jvm/java/include/linux \
-I/usr/lib/jvm/java/include \
-I/home/jni \
-fPIC -shared \
-o libTestJni.so TestJni.c
这里/usr/lib/jvm/java/include目录是jdk目录中的,得根据实际安装的jdk开发环境来决定。
libTestJni.so是动态链接库名称,一个库的必须要是下面格式
lib + 库名 + .so
链接的时候只需要提供库名(TestJni)就可以了。
-I/home/qgy/jni使我们刚才生成的.h文件的位置
完成上面操作之后,需要把.so文件放到java系统默认的库寻找目录才可以被jni正常调用。
如何查看该目录的位置?
可以写一个简单的程序来查询
public class Main{
public static void main(String[] args){
String[] split = System.getProperty("java.library.path").split(":");
for (String string : split) {
System.out.println(string);
}
}
}
编译运行上面程序代码就可以,输出java.library.path的位置,下面列出的每一个位置都是放置我们刚才生成的.os文件
/usr/java/packages/lib/amd64
/usr/lib64
/lib64
/lib
/usr/lib
将刚才生成的libTestJni.so放置到/lib目录下,这样编写的库就可以在java被加载,然后调用到
主程序
import java.util.*;
public class HelloWorld{
public static void main(String[] args){
new TestJni().print("Hello,Wolrd!");
}
}
编译运行就可以看到结果。
java HelloWorld
2. JNI 详解
2.1 Java & 类型对应
| Java | C/C++ | 字节数 |
|---|---|---|
| boolean | jboolean | 1 |
| byte | jbyte | 1 |
| char | jchar | 2 |
| short | jshort | 2 |
| int | jint | 4 |
| long | jlong | 8 |
| float | jfloat | 4 |
| double | jdouble | 8 |
数组类型
| Java | C/C++ |
|---|---|
| boolean[ ] | JbooleanArray |
| byte[ ] | JbyteArray |
| char[ ] | JcharArray |
| short[ ] | JshortArray |
| int[ ] | JintArray |
| long[ ] | JlongArray |
| float[ ] | JfloatArray |
| double[ ] | JdoubleArray |
2.2 S0生成
gcc SOURCE_FILES -fPIC -shared -o TARGET
SOURCE_FILES可以是.c文件,也可以是经过-c编译出来的.o文件
2.3 参数传递/返还
2.3.1 参数传入/出
public native void giveArray(int[] array);
int[] array = {9,100,10,37,5,10};
//排序
t.giveArray(array);
for (int i : array) {
System.out.println(i);
}
c实现代码
int compare(int *a,int *b){
return (*a) - (*b);
}
//传入
JNIEXPORT void JNICALL Java_com_study_jni_JniTest_giveArray
(JNIEnv *env, jobject jobj, jintArray arr){
//jintArray -> jint指针 -> c int 数组
jint *elems = (*env)->GetIntArrayElements(env, arr, NULL);
//printf("%#x,%#x\n", &elems, &arr);
//数组的长度
int len = (*env)->GetArrayLength(env, arr);
//排序
qsort(elems, len, sizeof(jint), compare);
(*env)->ReleaseIntArrayElements(env, arr, elems, JNI_COMMIT);
}
ReleaseXXXXArrayElements 方法中mode参数意义:
-
0,Java数组进行更新,并且释放C/C++数组。 -
JNI_ABORT,Java数组不进行更新,但是释放C/C++数组。 -
JNI_COMMIT,Java数组进行更新,不释放C/C++数组(函数执行完,数组还是会释放)。
2.3.2 参数返还
int[] array2 = t.getArray(10);
System.out.println("------------");
for (int i : array2) {
System.out.println(i);
}
c实现
//返回数组
JNIEXPORT jintArray JNICALL Java_com_study_jni_JniTest_getArray(JNIEnv *env, jobject jobj, jint len){
//创建一个指定大小的数组
jintArray jint_arr = (*env)->NewIntArray(env, len);
jint *elems = (*env)->GetIntArrayElements(env, jint_arr, NULL);
int i = 0;
for (; i < len; i++){
elems[i] = i;
}
//同步
(*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0);
return jint_arr;
}
3. 工程化
3.1 API封装
为了在各种各样的地方使用我们封装好的jni,需要下面准备
- 工程化TestJni,并封装jar
- 重新制作jni的
.so库文件
3.1.1 工程化TestJni
为了方便起见,下面直接使用IDEA进行先关操作。
创建Maven项目(采用了Maven是为了更加轻易的封装jar包)。

在java目录的下面创建刚才创建的包名com.demo

创建我们的jni类,复制TestJni.java内容如下
package com.demo;
/**
* create by Cliven on 2018-06-23 10:34
*/
public class TestJni {
/**
* 声明原生函数,jni接口
*
* @param content 输入参数
*/
public native static void print(String content);
//加载本地库代码
static {
System.loadLibrary("TestJni");
}
}
就是比刚才的TestJni多了package行

目前为止,已经将一个项目简单的建立起来了,现在需要把这个封装成jar供其他程序调用
3.1.2 封装
如果采用的是IEAD,那么从侧栏目找到Maven Projects

直接运行Lifecycle的install

运行完成后可以在target目录下找到刚才生成的jar包

3.2 库文件重做
经过上面的封装TestJni被加上了包,所以需要重新生成so文件
将带有包名的TestJni.java文件复制到Linux系统中,然后执行命令编译
javac ./TestJni.java -d .
编译完成后会在目录下生成一个由包名称组成的目录com/demo,编译好的文件就在这里面
[root@localhost jni]# ll
总用量 4
-rw-r--r--. 1 root root 341 6月 23 10:54 TestJni.java
[root@localhost jni]# javac TestJni.java -d .
[root@localhost jni]# ll
总用量 4
drwxr-xr-x. 3 root root 18 6月 23 10:54 com
-rw-r--r--. 1 root root 341 6月 23 10:54 TestJni.java
[root@localhost jni]#
现在使用TestJni.java所在目录下运行javah生成.h接口文件,这里必须使用完整的包名和类名才可运行否则会提示错误: 找不到 'TestJni' 的类文件。
javah -jni com.demo.TestJni
运行后会在目录中生成com_demo_TestJni.h,找上面的思路,实现这个.h文件,内容与上面的TestJni.c相同
#include <jni.h>
#include <stdio.h>
#include "com_demo_TestJni.h"
/*
* Class: com_demo_TestJni
* Method: print
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_demo_TestJni_print
(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;
}
编译,然后生成.so库文件
cc -I/usr/lib/jvm/java/include/linux \
-I/usr/lib/jvm/java/include \
-I./ \
-fPIC -shared \
-o libTestJni.so com_demo_TestJni.c
保存生成的.so文件,以后这个文件将和jar配套使用。
测试
复制.so文件到/lib中
cp libTestJni.so /lib
编写测试主函数
import com.demo.TestJni;
public class Main{
public static void main(String[] args){
TestJni.print("Guest");
}
}
编译运行,测试
javac Main.java -d .
java Main
[root@localhost jni]# vim Main.java
[root@localhost jni]# javac Main.java -d .
[root@localhost jni]# java Main
Hello --> Guest
[root@localhost jni]# ll
总用量 28
drwxr-xr-x. 3 root root 18 6月 23 10:54 com
-rw-r--r--. 1 root root 411 6月 23 11:12 com_demo_TestJni.c
-rw-r--r--. 1 root root 433 6月 23 11:11 com_demo_TestJni.h
-rwxr-xr-x. 1 root root 8040 6月 23 11:12 libTestJni.so
-rw-r--r--. 1 root root 337 6月 23 11:14 Main.class
-rw-r--r--. 1 root root 129 6月 23 11:13 Main.java
-rw-r--r--. 1 root root 348 6月 23 11:11 TestJni.java
到此已经测试jni的.so文件是可用的。
3.3 其他 Springboot 引入jar包
如何在springboot项目中使用上面的jar包
- 将
helloworld-1.0.0.jar放置到resources/lib目录中 - 在
build > resources >下面加入下面内容
<resource>
<directory>${basedir}/src/main/resources</directory>
<targetPath>BOOT-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
- 增加依赖项
dependencies >
<dependency>
<groupId>com.demo</groupId>
<artifactId>hellowrd</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/helloworld-1.0.0.jar</systemPath>
</dependency>
参考
使用JNI进行Java与C/C++语言混合编程(1)--在Java中调用C/C++本地库
Linux下JNI的使用
在 Linux 平台下使用 JNI
gcc编译参数-fPIC的一些问题
JNI 传递参数和返回值
SpringBoot使用本地jar包









