Javaネイティブインターフェース

Javaネイティブ・インタフェース(またはネイティブ・メソッド・インタフェース)は、Java以外のプログラミング・フレームワーク向けに設計された外部関数インタフェースです。JNIは、Javaコードからネイティブ呼び出し[ a ] 、ネイティブアプリケーション(ハードウェアおよびオペレーティングシステム・プラットフォーム固有のプログラム)、およびCC++アセンブリ言語などの他の言語で記述されたライブラリを呼び出すことを可能にします。[ 1 ] [ 2 ] [ 3 ]

Java 22では、Java Native Interfaceの後継とも言えるForeign Function and Memory APIが導入されました。 [ 4 ] [ 5 ]

目的

JNIを使​​用すると、プログラマーはネイティブメソッドを記述することで、アプリケーション全体をJavaプログラミング言語で記述できない場合(例えば、標準Javaクラスライブラリがプラットフォーム固有の機能やプログラムライブラリをサポートしていない場合)に対処できます。また、既存のアプリケーション(別のプログラミング言語で記述されたもの)をJavaアプリケーションからアクセスできるように変更するためにもJNIが使用されます。多くの標準ライブラリクラスは、ファイルI/Oやサウンド機能など、開発者とユーザーに機能を提供するためにJNIに依存しています。パフォーマンスとプラットフォームに依存するAPI実装を標準ライブラリに含めることで、すべてのJavaアプリケーションが安全かつプラットフォームに依存しない方法でこれらの機能にアクセスできるようになります。

JNIフレームワークにより、ネイティブメソッドはJavaコードがJavaオブジェクトを使用するのと同じ方法でJavaオブジェクトを使用できます。ネイティブメソッドはJavaオブジェクトを作成し、それらのオブジェクトを検査してタスクを実行することができます。また、ネイティブメソッドはJavaアプリケーションコードによって作成されたオブジェクトを検査して使用することもできます。

アプリケーションと署名されたアプレットだけが JNI を呼び出すことができます。

JNI に依存するアプリケーションは、Java が提供するプラットフォームの移植性を失います (部分的な回避策としては、プラットフォームごとに JNI コードの個別の実装を記述し、Java にオペレーティング システムを検出させて実行時に正しいオペレーティング システムをロードさせることです)。

ネイティブコードはJavaとインターフェースできるだけでなく、 Java AWTネイティブインターフェースCanvasを使えばJavaの要素も利用できます。プロセスはほぼ同じですが、若干の変更点があります。Java AWTネイティブインターフェースはJ2SE 1.3以降でのみ利用可能です。

JNIはCブリッジを経由せずにアセンブリコードに直接アクセスすることもできます。[ 6 ]アセンブリからJavaアプリケーションにアクセスすることも可能です。[ 7 ]

さらに、JNIはJVMの起動を開始するために使用される言語間のエントリポイントです。[ 8 ] [ 9 ] [ 10 ] [ 11 ]

マッピングタイプ

次の表はJavaネイティブコード間のプリミティブ型のマッピングを示しています。[ 12 ]

これらのタイプは、次のような特定のアプローチ専用です。

- Java 型は、 Java で使用できる デフォルトのプリミティブ型です。

- JVM 型シグネチャは、Java 上で一致する型を識別するために JVM によって使用される識別子です。

- JNI ネイティブ タイプ(ネイティブ タイプとも呼ばれます) は、C/C++ タイプにマップするために JNI 自体によって定義された「型」/構造体です。

これには共通の用途があり、これらの異なる変換の観点のタイプ間には相互運用性があり、各タイプはドメインに固有の別のタイプと一致する可能性があります。

Java型 JNIからネイティブ型へ 説明 JVM型シグネチャ
ブール値jブール値符号なし8ビット Z
バイトjバイト符号付き8ビット B
チャーjchar符号なし16ビット C
短いjshort符号付き16ビット S
整数ジント符号付き32ビット
長さjlong符号付き64ビット J
フロートjfloat32ビット F
ダブルjダブル64ビット D
空所空所適用できない V

さらに、JVM の型シグネチャは、"L fully-qualified-class ;"その名前で一意に指定されるクラスを意味します。例えば、シグネチャは"Ljava/lang/String;"クラス を参照しますjava.lang.String。また、[シグネチャに をプレフィックスとして付けると、その型の配列が作成されます。例えば、 は[Iint 配列型 ( int[]) を意味します。最後に、voidシグネチャはコード を使用しますV

jintこれらの型は互換性があります。通常 を使用する場所でを使用できint、その逆も同様です。型変換は必要ありません。ただし、Java の文字列および配列とネイティブの文字列および配列のマッピングは異なります。 をjstring使用する場所で を使用するとchar *、JVM がクラッシュする可能性があります。

デザイン

JNIフレームワークでは、ネイティブ関数は個別の.cまたは.cppファイルに実装されます(C++はJNIとのインターフェースをやや簡素化しています)。JVMが関数を呼び出す際、JNIEnvポインタ、jobjectポインタ、そしてJavaメソッドで宣言されたJava引数が渡されます。例えば、次のコードはJava文字列をネイティブ文字列に変換します。

外部"C" {JNIEXPORT void JNICALL Java_ClassName_MethodName ( JNIEnv * env , jobject obj , jstring javaString ) { const char * nativeString = env -> GetStringUTFChars ( javaString , 0 );// NativeString を使って何かするenv -> ReleaseStringUTFChars ( javaString nativeString ); }}

ポインタenvは、JVMへのインターフェースを含む構造体です。JVMとのやり取りやJavaオブジェクトの操作に必要なすべての関数が含まれています。JNI関数の例としては、ネイティブ配列とJava配列の相互変換、ネイティブ文字列とJava文字列の相互変換、オブジェクトのインスタンス化、例外のスローなどがあります。基本的に、Javaコードで実行できることはすべて を使って実行できますJNIEnvが、その簡単さはかなり劣ります。

引数はobj、このネイティブ メソッドが宣言されている Java オブジェクトへの参照です。

ネイティブデータ型はJavaデータ型と相互にマッピングできます。オブジェクト、配列文​​字列などの複合型の場合、ネイティブコードは 内のメソッドを呼び出して明示的にデータを変換する必要がありますJNIEnv

JNI環境ポインタ(JNIEnv*)は、Javaメソッドにマッピングされた各ネイティブ関数の引数として渡され、ネイティブメソッド内でJNI環境とのやり取りを可能にします。このJNIインターフェースポインタは保存できますが、現在のスレッド内でのみ有効です。他のスレッドは、まずAttachCurrentThread()を呼び出してVMにアタッチし、JNIインターフェースポインタを取得する必要があります。アタッチされたネイティブスレッドは、ネイティブメソッド内で実行される通常のJavaスレッドと同様に動作します。ネイティブスレッドは、DetachCurrentThread()を呼び出して自身をデタッチするまで、VMにアタッチされたままになります。[ 13 ]

JNIフレームワークは、ネイティブ側で実行されるコードによって割り当てられたJVM以外のメモリリソースに対して、自動ガベージコレクションを提供しません。したがって、ネイティブ側コード(アセンブリ言語など)は、ネイティブコードが取得したメモリリソースを明示的に解放する責任を負います。

LinuxおよびSolarisプラットフォームでは、ネイティブコードが自身をシグナルハンドラとして登録すると、JVM宛てのシグナルを傍受する可能性があります。責任連鎖(Chain of Responsibility)を使用することで、ネイティブコードとJVMの相互運用性を向上させることができます。Windowsプラットフォームでは、構造化例外処理(SEH)を使用してネイティブコードをSEH try/catchブロックでラップすることで、マシン(CPU/FPU)によって生成されたソフトウェア割り込み(NULLポインタアクセス違反やゼロ除算など)を捕捉し、割り込みがJVM(つまりJava側コード)に伝播されて未処理の例外が発生する可能性が高くなる前に、これらの状況を処理することができます。

NewStringUTF、GetStringUTFLength、GetStringUTFChars、ReleaseStringUTFChars、GetStringUTFRegion関数で使用されるエンコーディングは「修正UTF-8[ 14 ]であり、これはすべての入力に対して有効なUTF-8ではなく、実際には異なるエンコーディングです。ヌル文字(U+0000)と基本多言語面上にないコードポイント(U+10000以上、つまりUTF-16サロゲートペアとして表現されるもの)は、修正UTF-8では異なる方法でエンコードされます。多くのプログラムは実際にはこれらの関数を誤って使用しており、返されるUTF-8文字列または関数に渡されるUTF-8文字列を、修正UTF-8文字列ではなく標準UTF-8文字列として扱っています。プログラムでは、リトルエンディアン アーキテクチャでは UTF-16LE エンコーディングを使用し、ビッグエンディアンアーキテクチャでは UTF-16BE を使用する NewString、GetStringLength、GetStringChars、ReleaseStringChars、GetStringRegion、GetStringCritical、および ReleaseStringCritical 関数を使用し、その後、UTF-16 から UTF-8 への変換ルーチンを使用する必要があります。

JNI の一般的な使用法は次のようになります。ヘッダーを含める必要があります<jni.h>

#include <stdio.h> #include <jni.h>JNIEXPORT void JNICALL Java_HelloJNI_sayHello ( JNIEnv * env jobject obj ) { printf ( "CからHello! \n " ); }

そして、Java では、関数は次のように呼び出されます。

public class HelloJNI { static { // "hello" はLinux上の libhello.so を参照します。System.loadLibrary ( "hello" ); // System.load () はライブラリをロードする別の方法です。}パブリックネイティブvoid sayHello ();パブリック静的void main String [] args {新しいHelloJNI ()。sayHello (); } }

パフォーマンス

JNIは特定の状況下では大きなオーバーヘッドとパフォーマンスの低下を招きます。[ 15 ]

  • JNI メソッドへの関数呼び出しは、特にメソッドを繰り返し呼び出す場合にはコストがかかります。
  • ネイティブ メソッドは JVM によってインライン化されず、メソッドは既にコンパイルされているためJIT コンパイルもできません。
  • Java配列は、ネイティブコードでアクセスするためにコピーされ、後でコピーし直される場合があります。その場合、コストは配列のサイズに比例します。
  • メソッドにオブジェクトが渡されるか、コールバックが必要な場合、ネイティブメソッドはJVMへの独自の呼び出しを行う可能性が高くなります。ネイティブコードからJavaのフィールド、メソッド、型にアクセスするには、リフレクションプログラミング(リフレクション)に似た仕組みが必要です。シグネチャは文字列で指定され、JVMからクエリされます。これは遅く、エラーが発生しやすい方法です。
  • Javaの文字列はオブジェクトであり、長さを持ち、エンコードされています。文字列へのアクセスや作成には、O(n)のコピーが必要になる場合があります。

外部関数とメモリAPI

java.lang.foreignJava 22では、Java Native Interfaceの一種の後継として見ることができるForeign Function and Memory APIが導入されました。 [ 16 ]これは、外部ライブラリに を組み込んで呼び出す必要がなくなったため、よりシンプルなインタフェースを持ち、一般的にJNIよりも定型文が少なくて済みます<jni.h>。Foreign Function and Memory APIでは キーワードは使用されません。領域ベースのメモリ管理nativeをサポートします。クラスは、JVMヒープの内部または外部に配置できる連続したメモリセグメントをモデル化し、 は を使って割り当てられ、 は割り当てられたメモリセグメントを支えるメモリ領域の有効期間を制御します。 MemorySegmentMemorySegmentArena

パッケージorg.wikipedia.examples ;java.lang.foreign.*をインポートしますパブリッククラス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 Foreign Function および Memory API は、生のメモリを直接割り当てるArena.allocate()とを通じて JVM ヒープ (つまり、オペレーティング システム ヒープ) の外部で直接メモリ割り当てを可能にし、一方でと はメモリ セグメントの割り当て解除/解放に使用されます。 MemorySegment.allocateNative()Arena.close()MemorySegment.close()

外部関数インターフェイスとして、Java から外部関数間にアクセスしたり、ライブラリ内のシンボルのアドレスを取得したり、外部関数シグネチャをモデル化したりするため のjava.lang.foreignを導入します。LinkerSymbolLookupFunctionDescriptor

パッケージorg.wikipedia.examples ;java.lang.foreign .*をインポートします。java.lang.invoke.MethodHandleをインポートしますパブリッククラス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 } } }

代替案

Microsoft独自のJava仮想マシン実装(Visual J++)には、Javaからネイティブコードを呼び出すための同様のメカニズム、Raw Native InterfaceRNI )が搭載されていました。さらに、Windows APIなど(ただしこれに限定されません)など、Javaを認識しない既存のネイティブコード( J/Direct)を簡単に呼び出す手段も備えていました。しかし、この実装をめぐるSunとMicrosoftの訴訟を受けて、 Visual J++はメンテナンスされなくなりました。

RNIはJNIよりも使い勝手が良かった。Java環境ポインタによる管理が不要だったからだ。その代わりに、すべてのJavaオブジェクトに直接アクセスできた。これを容易にするために、Javaクラスからヘッダーファイルを生成するツールが使用されていた。同様に、J/Directは、必要な中間ネイティブライブラリとJNIを使​​用するよりも使いやすかった。

Java Native Access (JNA)は、コミュニティ開発のライブラリであり、JavaプログラマがJNIを使​​用せずにネイティブ共有ライブラリに簡単にアクセスできるようにします。ただし、JNAを使用するには、依存するjarライブラリの再配布が必要です。JNIのコーディングが難しくなり、JNAの速度が遅くなるというトレードオフがあります。[ 17 ] JNIはJavaコアに組み込まれています。

他にはJavaランタイムインターフェースがある。[ 18 ]

Java 22以降では、定型句が削減されインターフェースが簡素化されたため、JNIよりも新しいForeign Function and Memory APIの使用が推奨されています。[ 16 ]

参照

注記

  1. ^ JNIEnv を使用するコード、例:

参考文献

参考文献