目次 | 前の項目 | 次の項目 | Java Native Interface 仕様 |
この章は JNI の主な設計の問題に焦点をあてています。本項の設計の問題のほとんどはネイティブメソッドと関連があります。呼び出し API については、第 5 章「呼び出し API」に掲載されています。
ネイティブメソッドは JNI インタフェースポインタを引数として受け取ります。したがって、VM が同じ Java スレッドからネイティブメソッドに複数の呼び出しを行う場合は、ネイティブメソッドに同じインタフェースポインタを渡すことが保証されています。しかし、ネイティブメソッドは、異なる Java スレッドからでも呼び出すことができるので、異なる JNI インタフェースポインタを受け取ることもあります。
System.loadLibrary
メソッドを使用してロードされます。 次の例では、クラス初期化メソッドが、ネイティブメソッド f
が定義されているプラットフォーム固有のネイティブライブラリをロードしています。
package pkg; class Cls { native double f(int i, String s); static { System.loadLibrary("pkg_Cls"); } }
System.loadLibrary
の引数は、プログラマによって任意に選択されたライブラリ名です。このシステムは、標準であってもプラットフォーム固有の方式に従ってライブラリ名をネイティブライブラリ名に変換します。たとえば、Solaris システムは pkg_Cls
の名前を libpkg_Cls.so
に変換するのに対して、Win32 システムは同じ pkg_Cls
の名前を pkg_Cls.dll
に変換します。
プログラマは、同じローダでクラスがロードされる限り、必要とするクラスがいくらあっても、その必要なすべてのネイティブメソッドを、単一ライブラリを使用して格納することができます。VM はクラスローダごとのロードされたネイティブライブラリのリストを内部的に維持します。ベンダーは、名前ができるだけ衝突しないネイティブライブラリ名を選択する必要があります。
基盤のオペレーティングシステムが動的リンクをサポートしない場合は、すべてのネイティブメソッドが VM と事前にリンク済みでなければなりません。この場合、VM は実際にはこのライブラリをロードすることなく System.loadLibrary
の呼び出しを完了します。
プログラマは JNI 関数 RegisterNatives()
を呼び出して、クラスと関連付けられたネイティブメソッドを登録することもできます。RegisterNatives()
関数は、静的リンクされた関数を使用する場合に特に有用です。
Java_
次の例では、ネイティブメソッド g
はロング名を使ってリンクする必要はありません。もう一方のメソッド g
がネイティブメソッドでないため、ネイティブライブラリにないからです。
class Cls1 { int g(int i); native int g(double d); }すべての Unicode 文字が有効な C 関数名に確実に変換される、単純な名前分解方式が採用されています。完全修飾クラス名の中で斜線 (/) の代わりに下線 (_) 文字を使用します。名前または型記述子が数字で始まることはないので、表 2-1 の例のように、
_0
、...、_9
をエスケープシーケンスに使用することができます。 ネイティブメソッドとインタフェース API の両方とも、所定のプラットフォーム上での標準ライブラリ呼び出し規則に従っています。たとえば、UNIX システムは C 呼び出し規則を使用するのに対して、Win32 システムは __stdcall を使用します。
残りの引数は、通常の Java メソッド引数に対応しています。ネイティブメソッド呼び出しは、呼び出し側ルーチンに結果を値で渡して戻します。第 3 章「JNI の型とデータ構造」で、Java 型と C 型の間のマッピングについて説明しています。
コード 例 2-1 に、C 関数を使用してネイティブメソッド f
を実装する例を示します。ネイティブメソッド f
は、次のように宣言されます。
package pkg; class Cls { native double f(int i, String s); ... }長い分解名
Java_pkg_Cls_f_ILjava_lang_String_2
を持つ C 関数は、ネイティブメソッド f
を実装します。
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( JNIEnv *env, /* interface pointer */ jobject obj, /* "this" pointer */ jint i, /* argument #1 */ jstring s) /* argument #2 */ { /* Obtain a C-copy of the Java string */ const char *str = (*env)->GetStringUTFChars(env, s, 0); /* process the string */ ... /* Now we are done with str */ (*env)->ReleaseStringUTFChars(env, s, str); return ... }常にインタフェースポインタ env を使用して Java オブジェクトを操作していることに注意してください。C++ を使用すると、コード 例 2-2 に示すように、多少すっきりしたコードを書くことができます。
extern "C" /* specify the C calling convention */ jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( JNIEnv *env, /* interface pointer */ jobject obj, /* "this" pointer */ jint i, /* argument #1 */ jstring s) /* argument #2 */ { const char *str = env->GetStringUTFChars(s, 0); ... env->ReleaseStringUTFChars(s, str); return ... }C++ では、余分な間接参照およびインタフェースポインタ引数がソースコードから消えています。しかし、基盤となる機構は C による場合とまったく同じです。C++ では、JNI 関数が、インラインメンバ関数として定義されますが、これらは展開されて、C の対応部分になります。
オブジェクトは、ローカル参照としてネイティブメソッドに渡されます。JNI 関数によって返される Java オブジェクトはすべてローカル参照です。JNI では、プログラマがローカル参照からグローバル参照を作成することができます。Java オブジェクトを扱う JNI 関数は、グローバルとローカルの両方の参照を受け入れます。ネイティブメソッドは、その結果としてグローバルまたはとローカルのどちらかの参照を VM に返すことになります。
ほとんどの場合、プログラマは、ネイティブメソッドが戻ったあと、VM に基づいてすべてのローカル参照を解放すべきです。しかし、プログラマが明示的にローカル参照を解放する必要がある場合もあります。たとえば、次のような状況があります。
ローカル参照は、これらが作成されたスレッドの中だけで有効です。ネイティブコードは、スレッド間でローカル参照を受渡ししてはなりません。
レジストリを実装するには、テーブル、連結リスト、またはハッシュテーブルを使用するなど、さまざまな方法があります。レジストリの中の項目の重複を避けるため参照のカウントが使用されることがありますが、JNI の実装では重複項目を検出し重複をなくす必要はありません。
ローカル参照は、厳密にネイティブスタックをスキャンしても、忠実に実装することはできません。ネイティブコードは、ローカル参照をグローバルまたはヒープデータ構造に格納することもあります。
不透明な参照を介してアクセス用関数を使用するオーバーヘッドは、C データ構造体へ直接アクセスする場合より高くなります。ほとんどの場合に Java プログラマはネイティブメソッドを使用して、このインタフェースのオーバーヘッドが目立たなくなるような重要な (自明的でない) タスクを実行していると考えられます。
ネイティブメソッドが VM に対して配列の内容をピンで留めるように求めることができるように、「ピニング」の概念を導入する解決策もあります。そのあとネイティブメソッドは、その要素を指すダイレクトポインタを受け取ります。しかし、このアプローチは次の 2 つのことを意味します。
第一に、Java 配列のセグメントとネイティブメモリバッファの間でプリミティブ配列要素をコピーするための関数のセットを提供します。ネイティブメソッドが大きな配列の中の少数要素だけにアクセスする必要しかない場合は、これらの関数を使用してください。
第ニに、プログラマは別の関数のセットを使用して、ピニングされたバージョンの配列要素を検索することができます。これらの関数が記憶域の割り当ておよびコピーを実行するには Java VM が必要なことを覚えておいてください。これらの関数が実際に配列をコピーできるかどうかは、次のように VM の実装によって決まります。
これによって、柔軟性が高くなります。ガベージコレクタアルゴリズムにより、指定配列ごとのコピーまたはピニングについて個別に判断することができます。たとえば、ガベージコレクタが小さなオブジェクトをコピーし、大きなオブジェクトをピニングすることもできます。
JNI の実装では、複数のスレッドで実行されているネイティブメソッドが、同時に同じ配列に確実にアクセスできるようにしなければなりません。たとえば、JNI はピニングされた配列ごとに内部カウンタを備えて、あるスレッドが、別のスレッドもピニングしている配列のピンを外すことがないようにしています。JNI はネイティブメソッドによる排他アクセスのためにプリミティブ配列をロックする必要がないことに注意してください。異なるスレッドから同時に Java 配列を更新すると、不測の結果を招きます。
f
を呼び出すには、ネイティブコードはまず次のようにメソッド ID を獲得します。
jmethodID mid =続いてネイティブコードは、次のようにメソッド探索の手間をかけずにメソッド ID を繰り返し使用することができます。
env->GetMethodID(cls, "f", "(ILjava/lang/String;)D");
jdouble result = env->CallDoubleMethod(obj, mid, 10, str);フィールドまたはメソッド ID では、VM がその ID が導き出されたクラスをアンロードしないように防ぐことはできません。クラスがアンロードされると、フィールドまたはメソッド ID は無効になります。 そのため、ネイティブコードで次の点を確認する必要があります。 延長された期間中にメソッドまたはフィールド ID を使用するかどうか
JNI は、フィールドまたはメソッド ID がどのように内部的に実装されているかには何の制約も課しません。
printf()
関数は、無効アドレスを受け取ると、通常は実行時エラーを起し、エラーコードを返しません。すべての起こり得るエラー条件についてチェックするように C ライブラリ関数に強制すると、ユーザコードで 1 回チェックしてまたライブラリでも行うというように、チェックが重複する可能性があります。
プログラマは不正なポインタや間違った型の引数を JNI 関数に渡してはなりません。これを行うと、システムの破壊状態または VM のクラッシュを含む、不測の結果に至ることがあります。
null
など) です。したがって、プログラマは次のことを行うことができます。
ExceptionOccurred()
を呼び出して、エラー状態のさらに詳細な記述が含まれている例外オブジェクトを取得する
ExceptionOccurred()
を呼び出して、Java メソッドの実行中に起こり得る例外が起きていないかチェックしなければならない
ArrayIndexOutOfBoundsException
または ArrayStoreException
をスローするものがある
ExceptionOccurred()
を使用して同期および非同期の例外があるかを明示的にチェックする
ネイティブメソッドでは、必要な場所 (他の例外のチェックがない密なループの中など) に ExceptionOccurred()
のチェックを挿入して、現在のスレッドが非同期な例外に適当な時間の範囲内で応答することを保証する必要があります。
ExceptionClear()
を呼び出してその例外をクリアしてから、その例外処理コードを実行できる
ExceptionOccurred()
、ExceptionDescribe()
、およびExceptionClear()
だけです。ExceptionDescribe()
関数は、未処理の例外についてのデバッグメッセージを出力します。
Java Native Interface 仕様 (1997 年 3 月 15 日に dkramer によって生成された HTML)
Copyright © 1996, 1997 Sun Microsystems, Inc.All rights reserved
コメントや訂正は、jni@java.sun.com までお送りください。