浅谈JNI(一)

初识JNI

Posted by Anriku on April 26, 2018

今天这篇博客主要是给大家介绍一下Java的JNI编程,通过这篇博客我们可以对Java的JNI进行一下初步的认识。今天主要对JNI进行基本的学习以及介绍简单静态本地方法的调用。首先说一下什么是JNI吧!JNI的全称是Java Native Interface(Java本地接口),这是Java进行本地编程的接口。它可以实现Java代码与C/C++或者是汇编语言所编写的程序的交互。

当然,能全部用Java代码来进行编写程序是最好的。但是有些情况还是会用到JNI。下面是主要的一些情况:

  • Java程序是不支持平台独立性的,如果你需要让你的Java程序只会在某个平台上使用。那么你可能会使用到JNI
  • 你已经有用其它的这些语言编写的程序了,如果你想直接拿到Java程序中使用的时候。你可能会用到
  • 你想让你的Java程序中的某个部分的代码有严格的时间控制你可能会用到JNI来进行汇编语言的编写

从Java程序调用C/C++程序

在这之前,我们先来看一下本地代码的处理流程:

本地代码处理流程

从上面我们来总结的一下本地代码编写的整个流程:

  • 首先是编写带本地方法的Java代码
  • 然后,获取带有本地方法的头文件。下面是获取的命令
//JDK8之前

//第一步
javac <.java文件>

//第二步
javah <编译的class文件名>

//JDK8或者之后
javac -h <.java文件的目录(一般都是当前目录直接用.)> <.java文件>
  • 编写本地方法的实现的C/C++程序
  • 编译出对应平台的动态链接库(Linux上面是.so文件,Windows上面是.dll文件,macOS上是.jnilib文件)。下面是各个平台编译出C动态链接库的命令(如果是编译出C++的动态链接库,用的gcc编译器直接改为g++然后对应的文件改成cpp文件就行)
//mac用户

//其中-dynamiclib表示生成动态链接库,-I指定一个搜索目录这里第一个是jni.h的目录,第二个是jni_md.h的目录,-o后面表示输出文件名(注意在mac上动态链接库的后缀是.jnilib)
gcc -dynamiclib -I /Library/Java/JavaVirtualMachines/<对应的JDK>/Contents/Home/include -I /Library/Java/JavaVirtualMachines/<对应的JDK>/Contents/Home/include/darwin/ <.c文件> -o <输出文件.jnilib>

//Windows用户
cl -I <对应的JDK>\include -I <对应的JDK>\include\win32 -LD <.c文件> -<输出文件.dll>

//Linux用户
gcc -fPIC -I <对应JDK>/include -I <对应JDK>/include/linux -shared -o <输出文件.so> <.c文件>
  • 调用Java程序就会执行对象的C/C++程序

上面列出了整个流程,现在我们来一个Hello JNI吧!

下面是Java的代码:

class JNITest{
	
	public static native String greeting();

	public static void main(String args[]){
		System.out.println(greeting());
	}

    //System.loadLibrary用来加载动态链接库
	static{
		System.loadLibrary("JNITest");
	}
}
  • 第一步:(我这里就直接说JDK8以后的操作了)通过下面的命令,我们可以获得JNITest的字节码和一个包含本地方法的头文件。
javac -h . JNITest.java

下面是头文件的代码:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest */

#ifndef _Included_JNITest
#define _Included_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JNITest
 * Method:    greeting
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_JNITest_greeting
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

里面有三个预处理命令,这里我就不具体来介绍了。包含_Included_JNITest是为了避免多个文件包含同一个头文件而导致程序错误的预处理命令;包含__cplusplus是当包含头文件的文件是C++文件的时候就加上extern "C"{}来表示这个函数用C进行编译连接。关于extern “C”有兴趣的可以看一下这篇博客C++项目中的extern “C” {}

  • 然后下一步我们编写C或者是C++程序是实现本地方法:
//C实现
#include <stdio.h>
#include "JNITest.h"

JNIEXPORT jstring JNICALL Java_JNITest_greeting
  (JNIEnv *env, jclass cl){
  		jstring str;
  		char greting[] = "Hello JNI!\n";
  		str = (*env)->NewStringUTF(env,greting);
  		return str;
}


//C++实现
#include <iostream>
#include "JNITest.h"

using namespace std;

//其中extern "C"就是避免C++混编方法名
extern "C"
JNIEXPORT jstring JNICALL Java_JNITest_greeting
  (JNIEnv *env, jclass cl){
  		jstring str;
  		char greting[] = "Hello JNI!\n";
  		//根据全0字节结尾的“改良UTF-8”字节序列,返回一个新的Java字符串对象,无法构建字符串是为NULL
  		str = env->NewStringUTF(greting);
  		return str;
}

其中的不同点,在于env指针和extern “C”。关于env指针我不在这里进行深入了。如果有兴趣的可以参考官方文档

上面我们看到了jstring在JNI中表示Java字符串。我们知道C中int和long在不同的平台下有不同的字节数,而Java中int和long是确定的,因此JNI对所有的Java基本类型进行了对应类型的定义,这些类型都是在Java类型前面加上了j。这些定义都通过typedef定义在jni.h中

  • 然后通过前面说的命令来生产动态链接库
  • 最后执行Java程序

总结

在今天我们简单的认识了JNI的作用以及使用JNI的一个流程,当然关于JNI的细节我在今天并没有详细的介绍毕竟如果要深入细节的话一篇博客会变得很长,还不如看官方文档了,那样也失去了写博客的意义。通过博客只是想让大家对JNI有个总体的认识,在大家通过Java相关书籍或者是官方文档的深入学习的时候,更加的得心应手。

参考

《Java核心技术 卷二》

JNI官方文档

转载请注明链接