JNI是Java Native Interface的缩写,是Java平台的本地调用,从Java1.1就成为了Java标准的一部分,它允许Java代码和其它语言的代码进行互相调用,只要调用约定支持即可,尤其和C/C++的互相调用。
虽然使用Java与本地编译的代码进行交互,会丧失平台的可移植性,但是在特定情况下,这些问题是可以接受的,如:
1.使用一些旧的库
2.需要操作系统交互
3.提高程序的性能
一、jni介绍
Java是通过定义native方法,然后用其它语言实现该方法,最后在Java运行时,动态地加载该方法实现,通过调用native的方法,进而实现Java的本地调用。
1.实现架构
JVM封装了各种操作系统的差异性,提供了jni技术,使得开发中可以通过Java程序调用到操作系统的函数,进而与其它技术进行交互。下图是Linux平台jni的调用流程。Java应用程序通过jni接口调用动态链接库*.so,来实现jni的功能。
2.类型映射
Java基本数据类型与C语言基本数据类型的对应。
3.常用方法简介
(1)GetStringUTFLength 以字节为单位返回字符串的UTF-8长度
// jsize (JNICALL *GetStringUTFLength)(JNIEnv *env, jstring str)
int len = (*env)->GetStringUTFLength(env, str);
(2) GetStringUTFChars 返回指向字符串的UTF-8字符数组的指针。该数组在被ReleaseStringUTFChars()释放前将一直有效
// const char* (JNICALL *GetStringUTFChars)(JNIEnv *env, jstring str, jboolean *isCopy)
const char *buf = (*env)->GetStringUTFChars(env, str, NULL);
当isCopy 为JNI_FALSE,不要修改返回值,不然将改变java.lang.String的不可变语义。 一般会把isCopy设为NULL,不关心Java VM对返回的指针是否直接指向java.lang.String的内容
(3) ReleaseStringUTFChars 通知虚拟机平台相关代码无需再访问utf,utf参数是一个指针,可利用GetStringUTFChars()获得
// void (JNICALL *ReleaseStringUTFChars)(JNIEnv *env, jstring str, const char* chars)
(*env)->ReleaseStringUTFChars(env, str, buf);
(4)NewStringUTF 利用UTF-8字符数组构造新java.lang.String对象
// jstring (JNICALL *NewStringUTF)(JNIEnv *env, const char *utf)
(*env)->NewStringUTF(env, "hello");
二、jni实现步骤
我们知道java是可以调用C/C++程序的,也就时JNI编程,我们以一个最简单的Helleworld!程序,下面的程序实在Ubuntu11.04上面实现的。
(1)首先,定义java类,在java类中声明native方法,如下:
public class Main {
static {
System.loadLibrary("main");
}
private native void jniTest(String str);
/**
* @param args
*/
public static void main(String[] args) {
Main main = new Main();
String str = "hello world!";
main.jniTest(str);
}
}
代码中有System.loadLibrary(“main”);也就是加载共享库,这个库是什么来头后面会说到。
该Main.cpp函数的路径放在 “/home/project/jniTest/src”
(2)通过javac编译java源文件,得到对应的class文件,这个大家都知道的。
javac Main.java
(3)通过javah生成头文件
javah Main
头文件的内容如下所示:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Main */
#ifndef _Included_Main
#define _Included_Main
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Main
* Method: jniTest
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Main_jniTest
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
(4)根据头文件编写对应的.c文件。
首先,先生成一个同名的.c文件。
vim Main.c
再去修改其内容。
#include "Main.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_Main_jniTest(JNIEnv *evn, jobject obj, jstring jstr)
{
const jbyte* str =(const jbyte*) (*evn)->GetStringUTFChars(evn, jstr, JNI_FALSE);
printf("%s\n", str);
(*evn)->ReleaseStringUTFChars(evn, jstr, (const char* )str);
return;
}
(5)根据.c文件编译生成动态共享库,即.so文件。在这儿生成共享库时使用GCC, 必须通知编译器在何处查找此 Java 本地方法的支持文件(支持文件在不同的系统路徑有所不同),并且显式通知编译器生成位置无关的代码,如下所示。
gcc -I/usr/java/jdk1.8.0_121/include/ -I/usr/java/jdk1.8.0_121/include/linux -fPIC -shared -o libmain.so Main.c
(6)生成的动态共享库还需要告诉动态链接程序此共享文件的路径,也就时系统环境变量:LD_LIBRARY_PATH,一般可以这样设定
export LD_LIBRARY_PATH=`/home/project/jniTest/src/libmain.so`:$LD_LIBRARY_PATH #‘地址’通过pwd查得
(7)在命令行执行Main.class就行了
[root@instance-4trd9j2v src]# java Main
hello world!