JNI(Java Native Interface, java本地接口),是一种编程框架,用于java虚拟机中的java程序与本地应用或者库相互调用,本地应用一般指的是C,C++或者汇编等语言编写的,并且被编译为本机硬件和操作系统的程序。
有些事情Java无法处理时,JNI允许程序员用其他编程语言来解决,例如,Java标准库不支持的平台相关功能或者程序库。也用于改造已存在的用其它语言写的程序,供Java程序调用。许多基于JNI的标准库提供了很多功能给程序员使用,例如文件I/O、音频相关的功能。当然,也有各种高性能的程序,以及平台相关的API实现,允许所有Java应用程序安全并且平台独立地使用这些功能。
JNI框架允许Native方法调用Java对象,就像Java程序访问Native对象一样方便。Native方法可以创建Java对象,读取这些对象,并调用Java对象执行某些方法。当然Native方法也可以读取由Java程序自身创建的对象,并调用这些对象的方法。
1、在使用JNI的过程中,可能因为某些微小的BUG,对整个JVM造成很难重现和调试的错误。
2、依赖于JNI的应用失去了Java的平台移植性(一种解决办法是为每个平台编写专门的JNI代码,然后在Java代码中,根据操作系统加载正确的JNI代码)。
3、JNI框架并没有对 non-JVM 内存提供自动垃圾回收机制,Native代码(如汇编语言)分配的内存和资源,需要其自身负责进行显式的释放。
4、Linux与Solaris平台,如果Native代码将自身注册为信号处理器(signal handler),就会拦截发给JVM的信号。可以使用 责任链模式 让 Native代码更好地与JVM进行交互。
5、Windows平台上,在SEH try/catch块中可以将结构化异常处理(SEH)用来包装Native代码,以捕获机器(CPU/FPU)生成的软中断(例如:空指针异常、被除数为0等),将这些中断在传播到JVM(中的Java代码)之前进行处理,以免造成未捕获的异常。
6、NewStringUTF、GetStringUTFLength、GetStringUTFChars、ReleaseStringUTFChars与 GetStringUTFRegion等编码函数处理的是一种修改的UTF-8,[3],实际上是一种不同的编码,某些字符并不是标准的UTF-8。 null字符(U+0000)以及不在Unicode字符平面映射中的字符(codepoints 大于等于 U+10000 的字符,例如UTF-16中的代理对 surrogate pairs),在修改的UTF-8中的编码都有所不同。 许多程序错误地使用了这些函数,将标准UTF-8字符串传入或传出这些函数,实际上应该使用修改后的编码。程序应当先使用NewString、GetStringLength、GetStringChars、ReleaseStringChars、GetStringRegion、GetStringCritical与ReleaseStringCritical等函数,这些函数在小尾序机器上使用UTF-16LE编码,在大尾序机器上使用UTF-16BE编码,然后再通过程序将 UTF-16转换为 UTF-8。
7、JNI在某些情况下可能带来很大的开销和性能损失:
JNI环境指针(JNIEnv*)作为每个映射为Java方法的本地幔数的第一个参数,使得本地幔数可以与JNI环境交互。这个JNI界面指针可以存储,但仅在当前线程中有效。其它线程必须首先调用AttachCurrentThread()把自身附加到虚拟机以获得JNI界面指针。一旦附加,本地线程运行就类似执行本地幔数的正常Java线程。本地线程直到执行DetachCurrentThread()把自身脱离虚拟机。
把当前线程附加到虚拟机并获取JNI界面指针:
JNIEnv *env;
(*g_vm)->AttachCurrentThread (g_vm, (void **) &env, NULL);
当前线程脱离虚拟机:
(*g_vm)->DetachCurrentThread (g_vm);
JNI框架中,native方法一般在独立的.c或者.cpp文件中实现,当JVM调用这些函数,就传递一个JNIEnv
指针,一个jobject
的指针,任何在Java方法中声明的Java参数。一个JNI函数看起来类似这样:
// .h头文件中的函数声明
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello
(JNIEnv *, jclass, jstring);
// 对应的java文件声明
package com.study.jnilearn;
public class HelloWorld {
public static native String sayHello(String name);}
JNIEnv指针的作用,包含了与JVM交互的函数和访问java对象的函数,例如本地数组转换成java数组的JNI函数,本地字符串转换成java字符串的JNI函数,实例化对象,抛出异常等,基本上java可以做到的事情,JNIEnv也可以。
本地数据类型与Java数据类型可以互相映射。对于复合数据类型,如对象,数组,字符串,就必须用JNIEnv
中的方法来显示地转换。
第二个参数jclass引用了一个java对象(包含了native方法)。
下表是java(JNI)与本地代码之间的数据类型映射:
本地类型 | java语言的类型 | 描述 | 类型签名(signature) |
unsigned char | jboolean | unsigned 8 bits | Z |
signed char | jbyte | signed 8 bits | B |
unsigned short | jchar | unsigned 16 bits | C |
short | jshort | signed 16 bits | S |
long | jint | signed 32 bits | I |
long long__int64 | jlong | signed 64 bits | J |
float | jfloat | 32 bits | F |
double | jdouble | 64 bits | D |
void | V |
方法签名模板:"L fully-qualified-class" ,由改名字指明的类。
例如,签名"Ljava/lang/String;"
是类java.lang.String
。带前缀[
的签名表示该类型的数组,如[I
表示整型数组。void
签名使用V
代码。
这些类型是可以互换的,如jint
也可使用 int
,不需任何类型转换。
但是,Java字符串、数组与本地字符串、数组是不同的。如果在使用char *
代替了jstring
,程序可能会导致JVM崩溃。
package jni.study;
import java.util.List;
public class HelloWorld {
native void func1();
native String func2();
native void func3(String str);
native String func4(String str);
native void func5(List list);
native List func6();
native static String test(String str);
public static void main(String[] args) {
test("Hello World !");
}
static {
System.loadLibrary("libuntitled5");
}
}
D:\workspace\jni_study>javac src/main/java/jni/study/HelloWorld.java
D:\workspace\jni_study>javah -jni -classpath src/main/java -d src/main/java/lib jni.study.HelloWorld.class
// -jni 参数表示将class中native方法生成jni规则的函数
// -classpath 参数表示class文件所在目录
// -d 参数表示生成的.h文件的位置
这里需要注意 ,java9开始,javah命令就不存在了。 报:bash: javah: command not found。javahYou use the javah tool to generate C header and source files from a Java class.https://docs.oracle.com/javase/9/tools/javah.htm#JSWOR687
java16为例,只需要执行命令:
D:\workspace\jni_study>javac -h ./src/main/java/c src/main/java/jni/study/HelloWorld.java
打开clion,创建c库项目,库类型选择动态库shared
删除生成的c和h文件,将java生成的头文件复制过来,并创建同名源文件
引入头文件后,因为clion环境中没有
#include
需要引入jdk中提供的头文件依赖。
clion依赖的c编译器(C:\MinGW\mingw64\x86_64-w64-mingw32\include)中,添加两个文件:C:\Java\jdk-16\include\jni.h 和 C:\Java\jdk-16\include\win32\jni_md.h
编写CMakeLists.txt文件,并重新加载
add_library(untitled5 SHARED jni_study_HelloWorld.h jni_study_HelloWorld.c)
//加载过程
"D:\app\Intellij\CLion 2020.3.3\bin\cmake\win\bin\cmake.exe" -DCMAKE_BUILD_TYPE=Debug -G "CodeBlocks - MinGW Makefiles" D:\workspace\untitled5
-- Configuring done
-- Generating done
-- Build files have been written to: D:/workspace/untitled5/cmake-build-debug
[Finished]
头文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class jni_study_HelloWorld */
#ifndef _Included_jni_study_HelloWorld
#define _Included_jni_study_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: jni_study_HelloWorld
* Method: func1
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_jni_study_HelloWorld_func1
(JNIEnv *, jobject);
/*
* Class: jni_study_HelloWorld
* Method: func2
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_study_HelloWorld_func2
(JNIEnv *, jobject);
/*
* Class: jni_study_HelloWorld
* Method: func3
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_jni_study_HelloWorld_func3
(JNIEnv *, jobject, jstring);
/*
* Class: jni_study_HelloWorld
* Method: func4
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_study_HelloWorld_func4
(JNIEnv *, jobject, jstring);
/*
* Class: jni_study_HelloWorld
* Method: func5
* Signature: (Ljava/util/List;)V
*/
JNIEXPORT void JNICALL Java_jni_study_HelloWorld_func5
(JNIEnv *, jobject, jobject);
/*
* Class: jni_study_HelloWorld
* Method: func6
* Signature: ()Ljava/util/List;
*/
JNIEXPORT jobject JNICALL Java_jni_study_HelloWorld_func6
(JNIEnv *, jobject);
/*
* Class: jni_study_HelloWorld
* Method: test
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_study_HelloWorld_test
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
源文件内容:
//
// Created by Administrator on 2021/12/4.
//
#include "jni_study_HelloWorld.h"
#ifdef __cplusplus
extern "C"
{
#endif
/*
* Class: jni_study_HelloWorld
* Method: func1
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_jni_study_HelloWorld_func1
(JNIEnv *jniEnv, jobject jobject1){}
/*
* Class: jni_study_HelloWorld
* Method: func2
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_study_HelloWorld_func2
(JNIEnv *jniEnv, jobject jobject1){
return NULL;
}
/*
* Class: jni_study_HelloWorld
* Method: func3
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_jni_study_HelloWorld_func3
(JNIEnv *jniEnv, jobject jobject1, jstring jstring1){}
/*
* Class: jni_study_HelloWorld
* Method: func4
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_study_HelloWorld_func4
(JNIEnv *jniEnv, jobject jobject1, jstring jstring1){
return NULL;
}
/*
* Class: jni_study_HelloWorld
* Method: func5
* Signature: (Ljava/util/List;)V
*/
JNIEXPORT void JNICALL Java_jni_study_HelloWorld_func5
(JNIEnv *jniEnv, jobject jobject1, jobject jobject2){}
/*
* Class: jni_study_HelloWorld
* Method: func6
* Signature: ()Ljava/util/List;
*/
JNIEXPORT jobject JNICALL Java_jni_study_HelloWorld_func6
(JNIEnv *jniEnv, jobject jobject1) {
return NULL;
}
/*
* Class: jni_study_HelloWorld
* Method: test
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_jni_study_HelloWorld_test
(JNIEnv *jniEnv, jclass jclass1, jstring jstring1) {
const char *c_str = (*jniEnv)->GetStringUTFChars(jniEnv, jstring1, 0);
if (c_str == NULL) {
return NULL;
}
printf("c output : %s\n", c_str);
(*jniEnv)->ReleaseStringUTFChars(jniEnv, jstring1, c_str);
return NULL;
}
#ifdef __cplusplus
}
#endif
将dll文件复制到java项目中,并在启动配置中加入参数:
-Djava.library.path=./src/main/java/lib
执行后:
c output : Hello World !
https://zh.wikipedia.org/wiki/JNIhttps://zh.wikipedia.org/wiki/JNI