JNIJava Native Interface,Java本地接口)是一种编程框架,使得Java虚拟机中的Java程序可以调用本地应用/或库,也可以被其他程序调用。 本地程序一般是用其它语言(CC++汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序。[1]

设计目的和功能

有些事情Java无法处理时,JNI允许程序员用其他编程语言来解决,例如,Java标准库不支持的平台相关功能或者程序库。也用于改造已存在的用其它语言写的程序,供Java程序调用。许多基于JNI的标准库提供了很多功能给程序员使用,例如文件I/O、音频相关的功能。当然,也有各种高性能的程序,以及平台相关的API实现,允许所有Java应用程序安全并且平台独立地使用这些功能。

JNI框架允许Native方法调用Java对象,就像Java程序访问Native对象一样方便。Native方法可以创建Java对象,读取这些对象,并调用Java对象执行某些方法。当然Native方法也可以读取由Java程序自身创建的对象,并调用这些对象的方法。

注意事项

  • 在使用JNI的过程中,可能因为某些微小的BUG,对整个JVM造成很难重现和调试的错误。
  • 仅有应用程序与签名的applet可以调用JNI。
  • 依赖于JNI的应用失去了Java的平台移植性(一种解决办法是为每个平台编写专门的JNI代码,然后在Java代码中,根据操作系统载入正确的JNI代码)。
  • JNI框架并没有对 non-JVM 内存提供自动垃圾回收机制,Native代码(如汇编语言)分配的内存和资源,需要其自身负责进行显式的释放。
  • LinuxSolaris平台,如果Native代码将自身注册为信号处理器(signal handler),就会拦截发给JVM的信号。可以使用 责任链模式 让 Native代码更好地与JVM进行交互。[2]
  • Windows平台上,在SEH try/catch块中可以将结构化异常处理(SEH)用来包装Native代码,以捕获机器(CPU/FPU)生成的软中断(例如:空指针异常、被除数为0等),将这些中断在传播到JVM(中的Java代码)之前进行处理,以免造成未捕获的异常。
  • 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。
  • JNI在某些情况下可能带来很大的开销和性能损失:[4]
    • 调用 JNI 方法是很笨重的操作,特别是在多次重复调用的情况下。
    • Native 方法不会被 JVM 内联,也不会被 即时编译 优化 ,因为方法已经被编译过了。
    • Java 数组可能会被拷贝一份,以传递给 native 方法,执行完之后再拷贝回去. 其开销与数组的长度是线性相关的。
    • 如果传递一个对象给方法,或者需要一个回调,那么 Native 方法可能会自己调用JVM。 访问Java对象的属性、方法和类型时,Native代码需要类似反射的东西。签名由字符串指定,通常从JVM中查询。这非常缓慢并且容易出错。
    • Java 中的字符串(String) 也是对象,有 length 属性,并且是编码过的. 读取或者创建字符串都需要一次时间复杂度为 O(n) 的复制操作.

JNI如何工作

在JNI框架,native方法一般在单独的.c或.cpp文件中实现。当JVM调用这些函数,就传递一个JNIEnv指针,一个jobject的指针,任何在Java方法中声明的Java参数。一个JNI函数看起来类似这样:

JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj)
{
    /*Implement Native Method Here*/
}

env指向一个结构包含了到JVM的界面,包含了所有必须的函数与JVM交互、访问Java对象。例如,把本地数组转换为Java数组的JNI函数,把本地字符串转换为Java字符串的JNI函数,实例化对象,抛出异常等。基本上,Java程序可以做的任何事情都可以用JNIEnv做到,虽然相当不容易。

例如,下面代码把Java字符串转化为本地字符串:

//C++ code
extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj, jstring javaString)
{
    //Get the native string from javaString
    const char *nativeString = env->GetStringUTFChars(javaString, 0);

    //Do something with the nativeString

    //DON'T FORGET THIS LINE!!!
    env->ReleaseStringUTFChars(javaString, nativeString);
}
/*C code*/
JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj, jstring javaString)
{
    /*Get the native string from javaString*/
    const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);

    /*Do something with the nativeString*/

    /*DON'T FORGET THIS LINE!!!*/
    (*env)->ReleaseStringUTFChars(env, javaString, nativeString);
}
/*Objective-C code*/
JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj, jstring javaString)
{
    /*DON'T FORGET THIS LINE!!!*/
    JNF_COCOA_ENTER(env);

    /*Get the native string from javaString*/
    NSString* nativeString = JNFJavaToNSString(env, javaString);

    /*Do something with the nativeString*/

    /*DON'T FORGET THIS LINE!!!*/
    JNF_COCOA_EXIT(env);
}

本地数据类型与Java数据类型可以互相映射。对于复合数据类型,如对象,数组,字符串,就必须用JNIEnv中的方法来显示地转换。

第2个参数obj引用到一个Java对象,在其中声明了本地方法。

类型映射

下表是Java (JNI)与本地代码之间的数据类型映射:

More information 本地类型, Java语言的类型 ...
本地类型 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
Close

签名"L fully-qualified-class ;"是由该名字指明的类。例如,签名"Ljava/lang/String;"是类java.lang.String。带前缀[的签名表示该类型的数组,如[I表示整型数组。void签名使用V代码。

这些类型是可以互换的,如jint也可使用 int,不需任何类型转换

但是,Java字符串、数组与本地字符串、数组是不同的。如果在使用char *代替了jstring,程序可能会导致JVM崩溃。

JNIEXPORT void JNICALL Java_ClassName_MethodName
        (JNIEnv *env, jobject obj, jstring javaString) {
    // printf("%s", javaString);        // INCORRECT: Could crash VM!

    // Correct way: Create and release native string from Java string
    const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
    printf("%s", nativeString);
    (*env)->ReleaseStringUTFChars(env, javaString, nativeString);
}

这种情况也适用于Java数组。下例对数组元素求和。

JNIEXPORT jint JNICALL Java_IntArray_sumArray
        (JNIEnv *env, jobject obj, jintArray arr) {
    jint buf[10];
    jint i, sum = 0;
    // This line is necessary, since Java arrays are not guaranteed
    // to have a continuous memory layout like C arrays.
    env->GetIntArrayRegion(arr, 0, 10, buf);
    for (i = 0; i < 10; i++) {
        sum += buf[i];
    }
    return sum;
}

JNIEnv*

JNI环境指针(JNIEnv*)作为每个映射为Java方法的本地函数的第一个参数,使得本地函数可以与JNI环境交互。这个JNI界面指针可以存储,但仅在当前线程中有效。其它线程必须首先调用AttachCurrentThread()把自身附加到虚拟机以获得JNI界面指针。一旦附加,本地线程运行就类似执行本地函数的正常Java线程。本地线程直到执行DetachCurrentThread()把自身脱离虚拟机。[5]

把当前线程附加到虚拟机并获取JNI界面指针:

JNIEnv *env;
(*g_vm)->AttachCurrentThread (g_vm, (void **) &env, NULL);

当前线程脱离虚拟机:

(*g_vm)->DetachCurrentThread (g_vm);

高级使用

本地AWT绘制

本地代码不仅可以与Java交互,也可以在Java Canvas绘图,使用Java AWT Native Interface英语Java AWT Native Interface

访问汇编代码

JNI允许直接访问汇编代码。[6] 也可以从汇编代码访问Java。[7]

Microsoft的RNI

Microsoft实现的Java虚拟机——Visual J++的类似的访问本地Windows代码的机制Raw Native InterfaceRNI)。

例子

HelloWorld

make.sh

#!/bin/sh

# openbsd 4.9
# gcc 4.2.1
# openjdk 1.7.0

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
javac HelloWorld.java
javah HelloWorld
gcc -shared libHelloWorld.c -o libHelloWorld.so
java HelloWorld

build.bat

:: Microsoft Visual Studio 2012 Visual C++ compiler
SET VC="C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC"
:: Microsoft Windows SDK for Windows 7 and .NET Framework 4 
SET MSDK="C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A"
:: Java 1.7.0 update 21
SET JAVA_HOME="C:\Program Files (x86)\Java\jdk1.7.0_21"

call %VC%\vcvarsall.bat

javac HelloWorld.java
javah HelloWorld
%VC%\bin\cl /I%JAVA_HOME%\include /I%JAVA_HOME%\include\win32 /I%VC%\include /I%VC%\lib /I%MSDK%\Lib libHelloWorld.c /FelibHelloWorld.dll /LD
java HelloWorld

HelloWorld.java

class HelloWorld
{
	private native void print();
	public static void main(String[] args)
	{
		new HelloWorld().print();
	}
	static{
		System.loadLibrary("HelloWorld");
	}
}

HelloWorld.h

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

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    print
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_print
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

libHelloWorld.c

 #include <stdio.h>
 #include "HelloWorld.h"

 JNIEXPORT void JNICALL
 Java_HelloWorld_print(JNIEnv *env, jobject obj)
 {
     printf("Hello World!\n");
     return;
 }

Invocation:

$ chmod +x make.sh
$ ./make.sh

参见

  • Java AWT Native Interface英语Java AWT Native Interface
  • Gluegen英语Gluegen, a Java tool which automatically generates the Java and JNI code necessary to call C libraries from Java code
  • P/Invoke, the .NET Framework method of calling native applications
  • SWIG, a multilanguage interface-generator for C and C++ libraries that can generate JNI code
  • Java Native Access provides Java programs easy access to native shared libraries without writing boilerplate code

参考文献

相关书籍

外部链接

Wikiwand in your browser!

Seamless Wikipedia browsing. On steroids.

Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.

Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.