Java本地接口

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程序自身創建的對象，並調用這些對象的方法。

## 注意事項

- 在使用JNI的過程中，可能因為某些微小的BUG，對整個JVM造成很難重現和調試的錯誤。
- 僅有應用程式與簽名的applet可以調用JNI。
- 依賴於JNI的應用失去了Java的平台移植性（一種解決辦法是為每個平台編寫專門的JNI代碼，然後在Java代碼中，根據作業系統載入正確的JNI代碼）。
- JNI框架並沒有對 non-JVM 記憶體提供自動垃圾回收機制，Native代碼（如彙編語言）分配的記憶體和資源，需要其自身負責進行顯式的釋放。
- Linux與Solaris平台，如果Native代碼將自身註冊為信號處理器（signal handler），就會攔截發給JVM的信號。可以使用 責任鏈模式 讓 Native代碼更好地與JVM進行交互。
- Windows平台上，在SEH try/catch塊中可以將結構化異常處理（SEH）用來包裝Native代碼，以捕獲機器（CPU/FPU）生成的軟中斷（例如：空指針異常、被除數為0等），將這些中斷在傳播到JVM（中的Java代碼）之前進行處理，以免造成未捕獲的異常。
- NewStringUTF、GetStringUTFLength、GetStringUTFChars、ReleaseStringUTFChars與 GetStringUTFRegion等編碼函數處理的是一種修改的UTF-8，實際上是一種不同的編碼，某些字符並不是標準的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 方法是很笨重的操作，特別是在多次重複調用的情況下。
- 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函數看起來類似這樣：

```c
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字串轉化為本地字串：

```cpp
//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
/*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);
}
```

```objc
/*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)與本地代碼之間的數據類型映射：

本地類型｜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 hide=yqualified-class ;"`是由該名字指明的類。例如，簽名`"Ljava/lang/String;"`是類`java.lang.String`。帶前綴`[`的簽名表示該類型的陣列，如`[I`表示整型陣列。`void`簽名使用`V`代碼。

這些類型是可以互換的，如`jint`也可使用 `int`，不需任何類型轉換。

但是，Java字串、陣列與本地字串、陣列是不同的。如果在使用`char *`代替了`jstring`，程序可能會導致JVM崩潰。

```cpp
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陣列。下例對數組元素求和。

```cpp
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把自身脫離虛擬機。

把當前線程附加到虛擬機並獲取JNI界面指針：

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

當前線程脫離虛擬機：

```
(*g_vm)->DetachCurrentThread (g_vm);
```

## 高級使用

### 本地AWT繪製

本地代碼不僅可以與Java交互，也可以在Java java/awt繪圖，使用Java AWT Native Interface。

### 訪問彙編代碼

JNI允許直接訪問彙編代碼。也可以從彙編代碼訪問Java。

## Microsoft的RNI

Microsoft實現的Java虛擬機——Visual J++的類似的訪問本地Windows代碼的機制Raw Native Interface（RNI）。

## 例子

### HelloWorld

make.sh

```bash
#!/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

```dos
:: 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

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

HelloWorld.h

```c
/* 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

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

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

Invocation:

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

## 參見

- Java AWT Native Interface
- 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

## 外部連結

- Oracle's JNI page for Java 6, including the JNI Specification
- Java Native Interface: Programmer's Guide and Specification - Book, copyright 2002.
- Best practices for using the Java Native Interface
- JNI Complete tutorial with examples
- GNU CNI Tutorial
- Multi-platform JNI Tutorial at Think-Techie.com
- A JNI Tutorial at CodeProject.com (Microsoft specific)
- JNI Tutorial at CodeToad.com
- Larger JNI example from Sun
- JNI video tutorial with Eclipse and Visual Studio
- JNI in XCode from Apple
- Exception handling in JNI
- HawtJNI Simplifies creating JNI libraries by code generating the JNI implementations using declarative annotations placed on your Java code.
- J/Link lets you call Java from Mathematica in a completely transparent way, and it also lets you use and control the Mathematica kernel from a Java program (Commercial)
- Jace is a toolkit designed to make it easy to write JNI-based programs
- JNIWrapper provides simplified access to native code from Java applications without using Java Native Interface.
- Java to Native Interface LGPL library to call native functions from Java
- Java Native Access Access to native libraries from Java without JNI
- NLink Another library for access to native libraries without JNI
- NativeCall – call native methods from Java without JNI Library to access native code without JNI
- JNIEasy Transparent Native Programming for C/C++, pure Java alternative to JNI using POJOS and JDO/JPA development style
- jni4net bridge between Java and .NET (intraprocess, fast, object oriented, open-source)
- Object-Oriented JNI Advanced Add-in for VC6 Object-Oriented JNI with a number of helpers that includes the standard JNI SDK for regular C++ (Commercial)
- Object-Oriented JNI for .NET1.1 (low-level) Object-Oriented JNI with a number of helpers that includes the standard JNI SDK for C#, Managed C++, VB#, J# (Commercial)
- Object-Oriented JNI for .NET2.0 (low-level) Object-Oriented JNI with a number of helpers that includes the standard JNI SDK for C#, Managed C++, VB#, J# (Commercial)
- OOJNI Add-in (C#,VB#) for VS2005/2008 Generates object-oriented JNI code in C# or VB# for Java classes selected, implements Java interfaces and Java native methods in VB# and C#. Java Class methods and fields (which are represented as .NET Class properties) to be wrapped can be filtered. (Commercial)
- eXtremeDB JNI uses Java annotations and reflection to enable Java applications to call the EXtremeDB database (written in C) without reliance on an external database definition language
- JavaToDPR , a Delphi Project (.dpr) Stub File Generator that allows one to write an Embarcadero Delphi DLL to handle the native methods declared in a Java .class file