Java Native Interface (JNI)とは
Java Native Interface (JNI) は、
Javaプラットフォーム上で、
Javaで記述されたプログラムと、Cや
C++などの他のプログラミング言語で記述されたネイティブコードを連携させるためのインターフェース仕様です。JNIを利用することで、
Java VM上で動作させるには処理速度が不利なプログラムをネイティブコードで実装し高速化したり、標準
Javaクラスライブラリからはアクセスできない
オペレーティングシステムやハードウェアの機能を呼び出したりできます。
JNIの構成
JNIは、主に以下の2つのAPIで構成されています。
1.
Javaからネイティブコードを呼び出すためのABI:
Javaプログラムからネイティブ関数を呼び出すためのインターフェースを提供します。
2.
ネイティブコードからJavaバイトコードを動作させるためのAPI: ネイティブコードから
Java VMを操作し、
Javaの機能を利用するためのインターフェースを提供します。
JNIの利点
- - パフォーマンスの向上: 計算量の多い処理をネイティブコードで実装することで、Java VM上での実行よりも高速化が期待できます。
- - システム機能へのアクセス: Java標準ライブラリでは利用できない、OSやハードウェア固有の機能を利用できます。
- - 既存のネイティブライブラリの利用: C/C++などで作成された既存のライブラリをJavaプログラムから利用できます。
JNIの注意点
- - パフォーマンスの低下: JNIによるネイティブコードの呼び出しは、VMの実行環境の一貫性を保つために、通常のJavaプログラムの実行時とは異なるメモリ管理や排他制御を必要とする場合があり、実行速度が低下する可能性があります。
- - プラットフォーム依存: ネイティブコードは特定のプロセッサアーキテクチャやOSに依存するため、Javaのみで記述されたプログラムよりも移植性や再利用性が低下します。
- - メモリ管理の複雑性: JNIではメモリ管理を適切に行わないとメモリリークが発生する可能性があります。
Javaからのネイティブコード呼び出し
以下では、C/
C++言語によるネイティブコードの記述例を用いて説明します。
ネイティブ関数の実装
ネイティブ関数は、C/
C++のソースファイルに実装します。
Java VMは、JNIEnvへのポインタ、
Javaクラスまたはインスタンスへのポインタ(jobject)、そして
Javaメソッドで定義された引数を介して、ネイティブ関数を呼び出します。
c
include
include
JNIEXPORT void JNICALL
Java_MyClass_myNativeFunction(JNIEnv
env, jobject thisObj, jstring javaString) {
// JNIEnvを使用してJavaの文字列をCの文字列に変換
const char nativeString = (
env)->GetStringUTFChars(env, javaString, 0);
// Cの標準ライブラリを使用して文字列を標準出力に出力
printf("ネイティブコードで受信: %s
", nativeString);
// 解放
(env)->ReleaseStringUTFChars(env, javaString, nativeString);
}
上記の例では、
Java側で`MyClass`というクラスの`myNativeFunction`メソッドを呼び出すと、対応するC/
C++の関数が実行されます。`JNIEnv
env`は、Java VMとやり取りするためのインターフェースを提供するポインタであり、`jobject thisObj`はJavaオブジェクトのインスタンスを指します。
Java側では、`System.loadLibrary()`でネイティブライブラリをロードし、`native`キーワードを使ってネイティブメソッドを宣言します。
java
public class MyClass {
static {
System.loadLibrary("myjni"); // ネイティブライブラリをロード
}
public native void myNativeFunction(String message);
public static void main(String[] args) {
MyClass myObj = new MyClass();
myObj.myNativeFunction("Javaからのメッセージ");
}
}
データ型のマッピング
Javaのデータ型とネイティブデータ型の間には、以下のマッピングが存在します。
Javaの型 | JNIの型 | C/C++の型 |
---|
:-- | :-- | :--- |
boolean | jboolean | unsigned char |
byte | jbyte | char |
char | jchar | unsigned short |
short | jshort | short |
int | jint | int |
long | jlong | long long |
float | jfloat | float |
double | jdouble | double |
void | void | void |
Object | jobject | void |
String | jstring | - |
array | jarray | - |
型シグネチャ
JNIでは、Javaの型を識別するために「型シグネチャ」が使用されます。
- - `L fully-qualified-class ;`:クラスを表現します。例えば、`Ljava/lang/String;`は`java.lang.String`クラスを表します。
- - `[`:配列を表現します。例えば、`[I`は`int[]`を表します。
JNIEnv
JNIのAPI関数の多くは、Java VM環境変数として`JNIEnv`へのポインタを受け取ります。
JNIEnvの有効範囲
Javaからネイティブメソッドを呼び出す際に、C/C++実装側の関数に渡される`JNIEnv`ポインタは、そのネイティブメソッド呼び出しの間のみ有効です。
スレッドとJNIEnv
`JNIEnv`はスレッド間で共有できません。Java VMに関連付けられていないC/C++スレッドで`JNIEnv`を取得するには、`AttachCurrentThread()`関数を使用してスレッドをVMにアタッチし、スレッドを終了する際には`DetachCurrentThread()`関数でデタッチする必要があります。
Javaクラスへのアクセス
JNIを通じて、C/C++からJavaのクラスをインスタンス化したり、メソッドを呼び出したり、フィールドを読み書きしたりできます。
c++
// java.lang.Mathクラスの静的フィールドを取得し、静的メソッドを呼び出す例
jclass mathClass = env->FindClass("java/lang/Math");
jfieldID piField = env->GetStaticFieldID(mathClass, "PI", "D");
jdouble piValue = env->GetStaticDoubleField(mathClass, piField);
jmethodID absMethod = env->GetStaticMethodID(mathClass, "abs", "(D)D");
jdouble absValue = env->CallStaticDoubleMethod(mathClass, absMethod, -3.14);
ローカル参照とグローバル参照
JNIのAPI関数が返却するJNIオブジェクト参照は、通常「ローカル参照」です。ローカル参照は、スタックフレームの制御フローがJava側に返ると自動的に削除されます。
長期間保持する場合は、「グローバル参照」を使用します。グローバル参照は、`NewGlobalRef()`関数でローカル参照から生成でき、`DeleteGlobalRef()`関数で明示的に削除する必要があります。
参照の管理
ローカル参照は、ガベージコレクションの対象とならないため、削除を忘れるとメモリリークが発生します。グローバル参照は明示的に解放する必要があります。
JNI参照の比較
2つのJNI参照が同じJavaオブジェクトを参照しているかどうかを調べるには、`IsSameObject()`関数を使用します。
まとめ
JNIは、Javaとネイティブコードを連携させるための強力なツールですが、使用には注意が必要です。適切なメモリ管理を行い、パフォーマンスを考慮した上で利用することが重要です。この記事がJNIの理解に役立つことを願っています。