Java Native Interface (または Native Method Interface) は、Java 以外のプログラミングフレームワーク向けに設計された外部関数インターフェースです。JNI を使用すると、 Javaコードがネイティブ コード (つまり、Cアプリケーション バイナリ インターフェイスを使用してインターフェースするコード)、ネイティブ アプリケーション (ハードウェアおよびオペレーティングシステムプラットフォームに固有のプログラム)、およびC、C++、アセンブリなどの他の言語で記述されたライブラリを呼び出したり、呼び出されたりすることができます。[ 1 ] [ 2 ] [ 3 ]JNIEnv
Java 22では、Java Native Interfaceの後継と見なせる外部関数およびメモリAPIが導入されました。 [ 4 ] [ 5 ]
JNI を使用すると、ネイティブ メソッドによってアプリケーションが Java プラットフォーム外の呼び出しを行うことができます。たとえば、標準 Javaクラスライブラリがプラットフォーム固有の機能や特定のプログラム ライブラリをサポートしていない場合などに利用できます。また、既存のアプリケーション (別のプログラミング言語で記述されている) を Java アプリケーションからアクセスできるように変更するためにも使用されます。多くの標準ライブラリクラスは、ファイル I/O やオーディオ API など、開発者とユーザーに機能を提供するために JNI に依存しています。JNI を呼び出すことができるのは、アプリケーションと署名付きアプレットのみです。
JNIフレームワークを使用すると、ネイティブコードからJavaオブジェクトを操作および利用できます。ネイティブメソッド(nativeJavaではキーワードで示される)は、Javaオブジェクトを作成し、これらのオブジェクトを検査して使用してタスクを実行できます。ネイティブメソッドは、Javaアプリケーションコードによって作成されたオブジェクトを検査して使用することもできます。
JNIに依存するアプリケーションは、Javaが提供するプラットフォームの移植性を失います(部分的な回避策としては、プラットフォームごとにJNIコードの実装を別々に記述し、Javaがオペレーティングシステムを検出して実行時に適切なものをロードするようにする方法があります)。
ネイティブコードはJavaとインターフェースできるだけでなく、Javaライブラリを利用することもできます。これは、 J2SE 1.3以降で利用可能なJava AWTネイティブインターフェースCanvasによって可能になります。
JNI は、 Cブリッジなしでアセンブリへの直接アクセスも可能にします。[ 6 ]アセンブリから Java アプリケーションにアクセスすることも同様の方法で可能です。[ 7 ]
JNIは、JVMの起動を開始するために使用される言語間のエントリポイントです。[ 8 ] [ 9 ] [ 10 ] [ 11 ]
Java型はJavaで利用可能なデフォルトのプリミティブ型であり、JVM型シグネチャはJavaで一致する型を識別するためにJVMが使用する識別子であり、ネイティブ型はネイティブ型にマッピングするためにJNI自体によって定義された型です。プリミティブ型については、次のtypedefが提供されています。[ 12 ]
さらに、JVM型シグネチャは、 その名前で一意に指定されたクラスを参照します。たとえば、シグネチャ(クラス ファイル内) はクラスを参照します。シグネチャにプレフィックスを付けると、その型の配列を表します。たとえば、は を参照し、は を参照します。最大次元数は 255 です。Lfully-qualified-class;Ljava.lang.String;Ljava/lang/String;java.lang.String[[Iint[][[Iint[][]
jintこれらの型は互換性があります。通常 を使用する場所でを使用できint、その逆も同様で、型変換は必要ありません。ただし、Java の文字列と配列からネイティブの文字列と配列へのマッピングは異なります。たとえば、jstringと はchar*互換性のある型ではなく、 と も互換性がありませjintArrayんint[]。
JNIにはさらに、Java参照型に関する以下の宣言が含まれています<jni.h>。[ 13 ]
#ifdef __cplusplus class _jobject {}; class _jclass : public _jobject {}; // ... typedef _jobject * jobject ; typedef _jclass * jclass ; // ... #else struct _jobject ; // ... typedef struct _jobject * jobject ; typedef jobject jclass ; // ... #endifここでは、すべてのJava参照型は、Javaオブジェクトへのハンドルとして機能する不透明な空型として定義されています。これらの型は逆参照してはなりません。[ 14 ]
さらに、以下の不透明な型がハンドルとして使用されます。
ただし、jfieldIDとjmethodIDは単なる不透明なハンドルであり、java.lang.reflect.Field/とは対応しないことに注意してくださいjava.lang.reflect.Method。
ネイティブコードとJavaコード間の通信は、 ABIとして機能するJNIによって仲介されます。
// jni.h ファイルstruct JNINativeInterface_ ; struct JNIEnv_ ;#ifdef __cplusplus typedef JNIEnv_ JNIEnv ; #else typedef const struct JNINativeInterface_ * JNIEnv ; #endifJNIEnvは関数の構造(関数ポインタの配列)にアクセスするために使用され、各関数は一意のポインタによって識別されます。[ 15 ]プログラミング言語(CまたはC++)によって、実装は若干異なる場合があります。[ 16 ]例えば、Cでは次のようになります。
// jni.h ファイルstruct JNINativeInterface_ {jint ( JNICALL * GetVersion ) // 特定の関数を識別するためのポインタの JNICALL 宣言。( JNIEnv * env ); // パラメータ env は、ポインタへのポインタである JNIEnv ポインタへのポインタです。jclass ( JNICALL * DefineClass ) ( JNIEnv * env , const char * name );jclass ( JNICALL * FindClass ) ( JNIEnv * env , const char * name );// その他の関数ポインタ... }JNIフレームワークでは、ネイティブ関数は個別の.cまたは.cppファイルに実装されます(C++はJNIとのインターフェースをやや簡素化します)。JVMが関数を呼び出すとき、 Javaメソッドで宣言されたJava引数、およびJava引数を渡します。たとえば、次のコードは次のように変換されます。JNIEnv*jobjectjstringconst char*
extern "C" {JNIEXPORT void JNICALL Java_ClassName_MethodName ( JNIEnv * env , jobject obj , jstring javaString ) { const char * nativeString = env -> GetStringUTFChars ( javaString , 0 );// ネイティブ文字列を使って何か処理を行うenv -> ReleaseStringUTFChars ( javaString , nativeString ); }}ポインタenvは、JVMへのインターフェースを含む構造体です。JVMとのやり取りやJavaオブジェクトの操作に必要なすべての関数が含まれています。Javaコードでできることはすべて、このポインタを使用して実行できますJNIEnv。引数はobj、このネイティブメソッドが宣言されているJavaオブジェクトへの参照です。
ネイティブデータ型は、 Javaデータ型との間でマッピングできます。オブジェクト、配列、文字列などの複合型の場合、ネイティブコードは、メソッドを呼び出してデータを明示的に変換する必要がありますJNIEnv。
JNI 環境ポインタ ( JNIEnv*) は、Java メソッドにマッピングされた各ネイティブ関数の引数として渡され、ネイティブ メソッド内で JNI 環境とのやり取りを可能にします。この JNI インターフェイス ポインタは保存できますが、現在のスレッド内でのみ有効です。他のスレッドは、まず を呼び出してAttachCurrentThread()VM にアタッチし、JNI インターフェイス ポインタを取得する必要があります。アタッチされると、ネイティブ スレッドはネイティブ メソッド内で実行される通常の Java スレッドのように動作します。ネイティブ スレッドは、 を呼び出して自身をデタッチするまで VM にアタッチされたままになりますDetachCurrentThread()。[ 17 ]
JNIフレームワークは、ネイティブ側で実行されるコードによって割り当てられた非JVMメモリリソースに対する自動ガベージコレクション機能を提供しません。したがって、ネイティブコードは取得したすべてのリソースを明示的に解放する責任を負います。
LinuxおよびSolarisプラットフォームでは、ネイティブコードがシグナルハンドラとして登録されている場合、JVM宛てのシグナルをインターセプトできます。責任連鎖を使用することで、ネイティブコードとJVM間の相互運用が可能になります。Windowsプラットフォームでは、構造化例外処理(SEH)を使用してネイティブコードをSEHブロックでラップしtry、マシン(CPU/FPU)で生成されたソフトウェア割り込み(ヌルポインタcatch参照解除やゼロ除算など)を捕捉し、割り込みがJVM(つまりJava側のコード)に伝播される前にこれらの状況を処理できます。
NewStringUTF()、、、および関数で使用されるエンコーディングは「修正UTF-8」[ 18 ]GetStringUTFLength()ですが、これはすべての入力に対して有効なUTF-8ではなく、異なるエンコーディングです。ヌル文字(U+0000)と基本多言語面(U+10000以上、つまりUTF-16でサロゲートペアとして表現されるもの)にないコードポイントは、修正UTF-8では異なる方法でエンコードされます。多くのプログラムは、これらの関数を誤って使用し、返された、または関数に渡されたUTF-8文字列を修正UTF-8文字列ではなく標準UTF-8文字列として扱います。プログラムは、リトルエンディアンアーキテクチャではUTF-16LEエンコーディングを、ビッグエンディアンアーキテクチャではUTF-16BEエンコーディングを使用する、、、、、、および関数を使用し、その後UTF-16からUTF-8への変換ルーチンを使用する必要があります。GetStringUTFChars()ReleaseStringUTFChars()GetStringUTFRegion()NewString()GetStringLength()GetStringChars()ReleaseStringChars()GetStringRegion()GetStringCritical()ReleaseStringCritical()
例えば、以下はJavaメソッドのネイティブ実装例ですHelloJNI::sayHello()。
#include <stdio.h> #include <jni.h>JNIEXPORT void JNICALL Java_HelloJNI_sayHello ( JNIEnv * env , jobject obj ) { printf ( "Hello from C! \n " ); }Javaでは、この関数は次のように呼び出されます。
public class HelloJNI { static { // "hello"は Linux 上の libhello.so を指します System.loadLibrary ( " hello " ) ; // System.load() はライブラリをロードする別の方法です。}public native void sayHello ();public static void main ( String [] args ) { new HelloJNI (). sayHello (); } }JNIは特定の状況下では相当なオーバーヘッドとパフォーマンスの低下を招きます。[ 19 ]
java.lang.foreignJava 22では、Java Native Interface の後継と見なせるForeign Function and Memory API が導入されました。 [ 20 ]外部ライブラリに を含める必要がなくなったため、JNI よりもインターフェースがシンプルになり、一般的に定型コードが少なくて済みます。Foreign <jni.h>Function and Memory API は、キーワードの使用をnative完全に抽象化しています。領域ベースのメモリ管理をサポートしています。クラスはjava.lang.foreign.MemorySegment、JVM ヒープの内外に存在できる連続したメモリセグメントをモデル化し、 は、割り当てられたメモリセグメントを支えるメモリ領域の寿命を制御する を使用java.lang.foreign.MemorySegmentして割り当てられます。java.lang.foreign.Arena
提供java.lang.foreign.Arenaされるアリーナの種類は以下のとおりです。[ 21 ]
パッケージorg.wikipedia.examples ;import java.lang.foreign.Arena ; import java.lang.foreign.MemorySegment ; import java.lang.foreign.ValueLayout ;public class ForeignMemoryExample { public static void main ( String [] args ) { try ( Arena arena = Arena . ofConfined ()) { MemorySegment segment = arena . allocate ( 5 * Double . BYTES ); for ( int i = 0 ; i < 5 ; ++ i ) { segment . setAtIndex ( ValueLayout . JAVA_DOUBLE , i , i * 1.1 ); }for ( int i = 0 ; i < 5 ; ++ i ) { double value = segment . getAtIndex ( ValueLayout . JAVA_DOUBLE , i ); System . out . printf ( "インデックス %d の値: %d%n" , i , value ); } } } }ある意味で、Java の外部関数およびメモリ API は、JVM ヒープ (つまりオペレーティングシステムのヒープ) の外部に直接メモリを割り当てることを可能にします。これは、生のメモリを直接割り当てる関数Arena.allocate()とMemorySegment.allocateNative()関数によって実現され、メモリセグメントの解放/割り当て解除には関数Arena.close()と関数が使用されます。MemorySegment.close()
外部関数インターフェースとして、このクラスjava.lang.foreign.Linkerは、Java から外部関数へのアクセス (Java との間でのアクセス)、java.lang.foreign.SymbolLookupライブラリ内のシンボルのアドレスの取得、およびjava.lang.foreign.FunctionDescriptor外部関数シグネチャのモデリングのために提供されます。
パッケージorg.wikipedia.examples ;import java.lang.foreign.Arena ; import java.lang.foreign.FunctionDescriptor ; import java.lang.foreign.Linker ; import java.lang.foreign.MemorySegment ; import java.lang.foreign.SymbolLookup ; import java.lang.foreign.ValueLayout ; import java.lang.invoke.MethodHandle ;public class ForeignFunctionExample { public static void main ( String [] args ) throws Throwable { Linker linker = Linker . nativeLinker (); SymbolLookup stdlib = linker . defaultLookup (); MethodHandle printf = linker . downcallHandle ( stdlib . findOrThrow ( "printf " ), FunctionDescriptor . of ( ValueLayout . JAVA_INT , ValueLayout . ADDRESS ) );MethodHandle strlen = linker.downcallHandle ( stdlib.findOrThrow ( " strlen " ) , FunctionDescriptor.of ( ValueLayout.JAVA_LONG , ValueLayout.ADDRESS ) ) ;try ( Arena arena = Arena.ofConfined ( )) { MemorySegment formatString = arena.allocateUtf8String ( " Hello, %s!\n" ) ; MemorySegment argString = arena.allocateUtf8String ( " World" ); printf.invokeExact ( formatString , argString ); // " Hello , World!" と出力long len = ( long ) strlen.invokeExact ( formatString ) ; // len = 5 } } }レイアウトはjava.lang.foreign.ValueLayout、以下で構成されるモデルによってモデル化されます。
プリミティブ型はValueLayout.JAVA_*ネイティブ型にマッピングされるときにレイアウトを受け取りますが、ポインタ型はValueLayout.ADDRESS(たとえばchar*、int**またはstruct Point*) を使用します。
さらに、定数MemorySegment.NULL(型java.lang.foreign.MemorySegment)は、ヌルアドレス()を表す長さゼロのネイティブセグメントでありnullptr、と同等ですMemorySegment.ofAddress(0)。
Java は、より複雑なネイティブ型を表現するための追加のメソッドを提供します。たとえば、次のネイティブ型[ 22 ]
struct Point { int x ; long y ; };union Choice { float a ; int b ; };以下のレイアウトを受け取ります。
import java.lang.foreign.MemoryLayout ; import java.lang.foreign.StructLayout ; import java.lang.foreign.UnionLayout ;StructLayout pointLayout = MemoryLayout.structLayout ( ValueLayout.JAVA_INT.withName ( " x " ) , MemoryLayout.paddingLayout ( 4 ) , ValueLayout.JAVA_LONG.withName ( " y " ) } ;UnionLayout choiceLayout = MemoryLayout.unionLayout ( ValueLayout.JAVA_FLOAT.withName ( " a " ) , ValueLayout.JAVA_INT.withName ( " b " ) ) ;Java Runtime InterfaceはNetscapeによって前身として作成された。[ 23 ]
Microsoft 独自の Java 仮想マシン ( Visual J++ ) の実装には、Java からネイティブ コードを呼び出すための同様のメカニズムがあり、Raw Native Interface (RNI) と呼ばれていました。さらに、Windows API (J/Direct) など、Java を認識しない既存のネイティブ コードを呼び出す簡単な方法も提供していました。しかし、この実装に関するSun と Microsoft の訴訟の後、 Visual J++ のメンテナンスは終了しました。RNI は、Java 環境ポインタの管理が不要で、すべての Java オブジェクトに直接アクセスできるため、JNI よりも使いやすかったです。これを実現するために、Java クラスからヘッダー ファイルを生成するツールが使用されました。同様に、J/Direct は、必要な中間ネイティブ ライブラリと JNI を使用するよりも使いやすかったです。
Java Native Access (JNA) は、JNI を使用せずに Java プログラマーがネイティブ共有ライブラリに簡単にアクセスできるようにするコミュニティ開発ライブラリです。ただし、これには依存する jar ライブラリの再配布が必要です。トレードオフは、JNI がより複雑で、JNA が遅く、JNI のように組み込みではないことです。[ 24 ]
歴史的に、GCC (GCC 7 まで) はGNU Compiler for Java を提供していました。このコンパイラはCompiled Native Interface (CNI) を提供し、C++と Java の直接的な相互運用を可能にしました。これは JNI 自体に大きく基づいた API を提供しました。[ 25 ] Java クラスをC++ クラスとして直接表現することで、JNI よりもいくつかの利点があると主張しました。[ 26 ]
Java 22以降、外部関数およびメモリAPIは、定型コードの削減とインターフェースの簡素化により、JNIに取って代わった。[ 20 ]