| 目次 | 前項目 | 次項目 | Java ネイティブインタフェース仕様 |
本章は JNI の主な設計の問題に焦点をあてています。本項の設計の問題のほとんどはネイティブメソッドと関連があります。呼び出し API については、第 5 章、「呼び出し API」に掲載されています。
ネイティブメソッドは JNI インタフェースポインタを引数として受け取ります。VM が同じ Java スレッドからネイティブメソッドに複数の呼び出しを行うとき、VM はネイティブメソッドの同じポインタに必ずアクセスするよう保証されています。しかし、ネイティブメソッドは、異なる 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() 関数は、静的リンクされた関数を用いるとき特に有用です。
次の例では、ネイティブメソッド 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 つの点を暗黙的に含みます。
第1に、Java 配列のセグメントとネイティブメモリバッファの間でプリミティブ配列要素をコピーするための関数のセットを提供します。ネイティブメソッドが大きな配列の中の少数要素だけにアクセスする必要しかない場合は、これらの関数を使用してください。
第2に、プログラマは別の関数のセットを使用して、ピン留めされたバージョンの配列要素を検索するすることができます。これらの関数は記憶域の割当ておよびコピーを実行するには 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 ネイティブインタフェース仕様 (1997年3月15日にdkramer によって生成されたHTML)
Copyright (C) 1996, 1997 Sun Microsystems, Inc.All rights reserved
コメントは、jni@java.sun.com宛てに送ってください。