Java本地介面
此條目翻譯品質不佳。 (2014年11月1日) |
JNI (Java Native Interface,Java本地介面)是一種編程框架,使得Java虛擬機器中的Java程式可以呼叫本地應用/或庫,也可以被其他程式呼叫。 本地程式一般是用其它語言(C、C++或組合語言等)編寫的,並且被編譯為基於本機硬體和作業系統的程式。[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代碼(如組合語言)分配的主記憶體和資源,需要其自身負責進行顯式的釋放。
- Linux與Solaris平台,如果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)與本地代碼之間的資料類型對映:
本地類型 | 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 |
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崩潰。
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。
訪問組譯代碼
Microsoft的RNI
Microsoft實現的Java虛擬機器——Visual J++的類似的訪問本地Windows代碼的機制Raw Native Interface(RNI)。
例子
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
- 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
參考文獻
- ^ Role of the JNI. The Java Native Interface Programmer's Guide and Specification. [2008-02-27]. (原始內容存檔於2012-06-26).
- ^ If JNI based application is crashing, check signal handling!. [2014-05-30]. (原始內容存檔於2014-11-09).
- ^ Modified UTF-8 Strings. [2014-05-30]. (原始內容存檔於2020-05-03).
- ^ java - What makes JNI calls slow? - Stack Overflow. [2017-01-22]. (原始內容存檔於2019-10-17).
- ^ The Invocation API. Sun Microsystems. http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/invocation.html (頁面存檔備份,存於網際網路檔案館)
- ^ Invoking Assembly Language Programs from Java. Java.net. 2006-10-19 [2007-10-06]. (原始內容存檔於2008-03-30).
- ^ Launch Java Applications from Assembly Language Programs. Java.net. 2006-10-19 [2007-10-04]. (原始內容存檔於2007-10-11).
相關書籍
- Gordon, Rob. Essential Jni: Java Native Interface 1st. Prentice Hall. March 1998: 498 [2014-05-30]. ISBN 0-13-679895-0. (原始內容存檔於2012-10-01).
- Liang, Sheng. Java(TM) Native Interface: Programmer's Guide and Specification 1st. Prentice Hall. June 20, 1999: 320 [2014-05-30]. ISBN 0-201-32577-2. (原始內容存檔於2012-10-01).
外部連結
- 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