目次|前 |
この呼び出しAPIにより、ソフトウェア・ベンダーはJava VMを任意のネイティブ・アプリケーションにロードできるようになります。そのベンダーの提供するJavaが実行可能なアプリケーションは、Java VMソース・コードにリンクする必要がありません。
この章では、呼び出しAPIの概要の説明から始めます。それ以降は、すべての呼び出しAPI関数のリファレンス・ページです。次のトピックについて説明します。
次のコード例では、呼び出しAPIの関数の使用方法について説明します。この例では、C++コードはJava VMを生成し、Main.test
と呼ばれるstaticメソッドを呼び出します。明確にするために、エラー・チェックを省略しました。
#include <jni.h> /* where everything is defined */ ... JavaVM *jvm; /* denotes a Java VM */ JNIEnv *env; /* pointer to native method interface */ JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */ JavaVMOption* options = new JavaVMOption[1]; options[0].optionString = "-Djava.class.path=/usr/lib/java"; vm_args.version = JNI_VERSION_1_6; vm_args.nOptions = 1; vm_args.options = options; vm_args.ignoreUnrecognized = false; /* load and initialize a Java VM, return a JNI interface * pointer in env */ JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); delete options; /* invoke the Main.test method using the JNI */ jclass cls = env->FindClass("Main"); jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V"); env->CallStaticVoidMethod(cls, mid, 100); /* We are done. */ jvm->DestroyJavaVM();
この例では、APIの3つの関数を使用しています。呼び出しAPIは、ネイティブ・アプリケーションがJNIインタフェース・ポインタを使用してVM機能にアクセスできるようにします。この設計はNetscapeのJRI埋込みインタフェースに似ています。
JNI_CreateJavaVM()
関数はJava VMをロードして初期化し、JNIインタフェース・ポインタへのポインタを返します。JNI_CreateJavaVM()
を呼び出したスレッドは、メイン・スレッドとみなされます。
JNIインタフェース・ポインタ(JNIEnv
)は、現在のスレッドでのみ有効です。別のスレッドがJava VMにアクセスする必要がある場合、これは最初にAttachCurrentThread()
を呼び出して、自身をVMに接続しJNIインタフェース・ポインタを取得する必要があります。一度VMに接続されると、ネイティブ・スレッドはネイティブ・メソッド内で実行中の普通のJavaスレッドのように機能します。ネイティブ・スレッドは、DetachCurrentThread()
を呼び出して自身を切り離すまでVMに接続されたままになります。
接続されたスレッドには、相当量の作業を実行するのに十分なスタック領域が必要です。スレッドごとのスタック領域の割当ては、オペレーティング・システム固有です。たとえば、pthreadを使用する場合は、pthread_create
へのpthread_attr_t
引数でスタック・サイズを指定できます。
VMに接続されたネイティブ・スレッドは、終了する前にDetachCurrentThread()
を呼び出して自身を分離する必要があります。呼出しスタック上にJavaメソッドがある場合、スレッドは分離できません。
JNI_DestroyJavaVM()
関数は、Java VMをアンロードします。
VMは、
現在のスレッドを実際にアンロードする前に、それがデーモンでない唯一のユーザー・スレッドになるまで待機します。ユーザー・スレッドには、Javaスレッドおよび接続されたネイティブ・スレッドの両方があります。この制限は、Javaスレッドまたは接続されたネイティブ・スレッドがロック、ウィンドウなどのシステム・リソースを保持している可能性があるために存在します。VM
は、自動的にこれらのリソースを解放することはできません。VMがアンロードされているときに、
現在のスレッドを実行中の唯一のスレッドに制限することにより、任意のスレッドが保持しているシステム・リソースを解放する負荷はプログラマに課せられます。
ネイティブ・ライブラリを一度ロードすると、すべてのクラス・ローダーからそのライブラリを認識できます。そのため、異なるクラス・ローダーの2つのクラスが、同じネイティブ・メソッドにリンクしてしまう可能性がありました。このため次の2つの問題が発生します。
各クラス・ローダーは、独自のネイティブ・ライブラリのセットを管理します。同じJNIネイティブ・ライブラリを、2つ以上のクラス・ローダーにロードすることはできません。そのようなことを行うと、UnsatisfiedLinkError
がスローされます。たとえば、System.loadLibrary
を使用して2つのクラス・ローダーにネイティブ・ライブラリをロードしようとすると、UnsatisfiedLinkError
がスローされます。この新しい手法の利点は次のとおりです。
バージョン管理およびリソース管理を容易にするために、JNIライブラリは次の2つの関数をオプションでエクスポートできます。
jint JNI_OnLoad(JavaVM *vm, void *reserved);
System.loadLibrary
を介して)ネイティブ・ライブラリがロードされると、VMはJNI_OnLoad
を呼び出します。JNI_OnLoad
は、ネイティブ・ライブラリが必要とするJNIバージョンを返さなければなりません。
新しいJNI関数のどれかを使用するために、ネイティブ・ライブラリはJNI_VERSION_1_2
を返すJNI_OnLoad
関数をエクスポートする必要があります。ネイティブ・ライブラリがJNI_OnLoad
関数をエクスポートしない場合、VMはライブラリがJNIバージョンJNI_VERSION_1_1
を要求しているだけであるとみなします。VMがJNI_OnLoad
によって返されたバージョン番号を認識しない場合、VMはライブラリをアンロードし、ライブラリがロードされなかったかのように動作します。
JNI_Onload_L(JavaVM *vm, void *reserved);
ライブラリLが静的にリンクされている場合、System.loadLibrary("L")
または同等のAPIの最初の呼出し時に、JNI_OnLoad
関数に指定されたものと同じ引数および期待戻り値で、JNI_OnLoad_L
関数が呼び出されます。JNI_OnLoad_L
は、ネイティブ・ライブラリが必要とするJNIバージョンを返さなければなりません。このバージョンはJNI_VERSION_1_8
以降である必要があります。VMがJNI_OnLoad_L
によって返されたバージョン番号を認識しない場合、VMはライブラリがロードされなかったかのように動作します。
ネイティブ・メソッド実装を含むネイティブ・ライブラリからエクスポートされます。
void JNI_OnUnload(JavaVM *vm, void *reserved);
JNI_OnUnload
を呼び出します。この関数は、クリーンアップ・オペレーションに使用されます。これは未確認のコンテキスト(ファイナライザからのコンテキストなど)で呼び出される関数なので、プログラマは慎重にJava VMサービスを使用する必要があります。またJavaコールバックを任意に行うことのないようにしなければなりません。
JNI_OnLoad
とJNI_OnUnload
は、JNIライブラリがオプションで提供する2つの関数であり、VMからエクスポートされるものではありません。
JNI_OnUnload_L(JavaVM *vm, void *reserved);
このような関数がエクスポートされた場合は、静的にリンクされているネイティブ・ライブラリLを含むクラス・ローダーでガベージ・コレクションが実行されると、VMによってライブラリのJNI_OnUnload_L
関数が呼び出されます。この関数は、クリーンアップ・オペレーションに使用されます。これは未確認のコンテキスト(ファイナライザからのコンテキストなど)で呼び出される関数なので、プログラマは慎重にJava VMサービスを使用する必要があります。またJavaコールバックを任意に行うことのないようにしなければなりません。
dlopen
など)を実行してネイティブ・ライブラリをロードするだけでは、この目標は完全には実現されないことに注意してください。通常、ネイティブ関数は、ライブラリをメモリーにロードして、ネイティブ・ライブラリにハンドルを返すホスト・オペレーティング・システムの呼出しを実行するために、Javaクラス・ローダーから呼び出されます。このハンドルは、以降のネイティブ・ライブラリのエントリ・ポイント検索の際に格納および使用されます。ライブラリを登録するためにハンドルが正常に返されると、Javaネイティブ・クラス・ローダーでロード・プロセスが完了します。
JavaVM
型は、呼び出しAPI関数表へのポインタです。次のコード例は、この関数表を示しています。
typedef const struct JNIInvokeInterface *JavaVM; const struct JNIInvokeInterface ... = { NULL, NULL, NULL, DestroyJavaVM, AttachCurrentThread, DetachCurrentThread, GetEnv, AttachCurrentThreadAsDaemon };
3つの呼び出しAPI関数JNI_GetDefaultJavaVMInitArgs()
、JNI_GetCreatedJavaVMs()
、およびJNI_CreateJavaVM()
は、Java VM関数ではありません。これらの関数は既存のJavaVM
構造体がなくても使用できます。
jint JNI_GetDefaultJavaVMInitArgs(void *vm_args);
Java VMのデフォルト構成を返します。この関数を呼び出す前に、ネイティブ・コードはvm_args->versionフィールドを、VMでサポートされると予測されるJNIバージョンに設定する必要があります。この関数から復帰すると、vm_args->versionは、VMがサポートする実際のJNIバージョンに設定されます。
Java仮想マシンを実装するネイティブ・ライブラリからエクスポートされます。
vm_args
: デフォルト引数が入るJavaVMInitArgs
構造体を指すポインタ。
要求されたバージョンがサポートされている場合はJNI_OK
を返し、要求されたバージョンがサポートされていない場合はJNIエラー・コード(負の数)を返します。
jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
作成されたJava VMをすべて返します。VMへのポインタは、作成された順にバッファvmBufに書き込まれます。しかし、エントリのbufLen番号しか書き込みません。作成されたVMの全体数は、*nVMで返します。
1つのプロセスでの複数のVMの作成はサポートされていません。
Java仮想マシンを実装するネイティブ・ライブラリからエクスポートされます。
vmBuf
: VM構造体が配置されるバッファへのポインタ。
bufLen
: バッファの長さ。
nVMs
: 整数を指すポインタ。
成功した場合はJNI_OK
を返し、失敗した場合は該当するJNIエラー・コード(負の数)を返します。
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
ロードして、Java VMを初期化します。現在のスレッドがメイン・スレッドになります。env
引数を、メイン・スレッドのJNIインタフェース・ポインタに設定します。
JNI_CreateJavaVM
の2番目の引数が常にJNIEnv *
を参照するポインタであるのに対して、3番目の引数は、オプション文字列を使用してすべてのVM起動オプションをエンコードするJavaVMInitArgs
構造体を参照するポインタです。
typedef struct JavaVMInitArgs { jint version; jint nOptions; JavaVMOption *options; jboolean ignoreUnrecognized; } JavaVMInitArgs;
options
フィールドは、次の型の配列です。
typedef struct JavaVMOption { char *optionString; /* the option as a string in the default platform encoding */ void *extraInfo; } JavaVMOption;
配列のサイズは、JavaVMInitArgs
のnOptionsフィールドに示されます。ignoreUnrecognized
がJNI_TRUE
の場合、JNI_CreateJavaVM
は、「-X
」または「_
」で始まるすべての認識できないオプション文字列を無視します。ignoreUnrecognized
がJNI_FALSE
の場合、JNI_CreateJavaVM
は認識できないオプション文字列を検出すると、ただちにJNI_ERR
を返します。すべてのJava仮想マシンは、次の標準オプションのセットを認識する必要があります。
optionString | 意味 |
---|---|
-D<name>=<value> |
システム・プロパティを設定する |
-verbose[:class|gc|jni] |
冗長出力を有効にする。各オプションの後に、VMが出力するメッセージの種類を示す、カンマで区切った名前のリストを続けることができる。たとえば、「-verbose:gc,class 」は、VMにGCとクラス・ローディング関連のメッセージを出力するように指示する。標準的な名前には、gc 、class 、およびjni 。標準でない(VM固有の)名前はすべて、「X 」で始まる必要がある。 |
vfprintf |
extraInfo は、vfprintf フックへのポインタ。 |
exit |
extraInfo は、exit フックへのポインタ。 |
abort |
extraInfo は、abort フックへのポインタ。 |
加えて、各VM実装は、標準でない独自のオプション文字列のセットをサポートします。標準でないオプション名は、「-X
」またはアンダースコア(「_
」)で始まる必要があります。たとえば、JDK/JREは-Xms
および-Xmx
オプションをサポートしているため、プログラマは初期および最大のヒープ・サイズを指定できます。「-X
」で始まるオプションは、「java
」コマンド行からアクセス可能です。
次の例は、JDK/JREでJava仮想マシンを作成するコードです。
JavaVMInitArgs vm_args; JavaVMOption options[4]; options[0].optionString = "-Djava.compiler=NONE"; /* disable JIT */ options[1].optionString = "-Djava.class.path=c:\myclasses"; /* user classes */ options[2].optionString = "-Djava.library.path=c:\mylibs"; /* set native library path */ options[3].optionString = "-verbose:jni"; /* print JNI-related messages */ vm_args.version = JNI_VERSION_1_2; vm_args.options = options; vm_args.nOptions = 4; vm_args.ignoreUnrecognized = TRUE; /* Note that in the JDK/JRE, there is no longer any need to call * JNI_GetDefaultJavaVMInitArgs. */ res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args); if (res < 0) ...
Java仮想マシンを実装するネイティブ・ライブラリからエクスポートされます。
p_vm
: 結果のVM構造体が配置される位置へのポインタ。
p_env
: メイン・スレッドのJNIインタフェース・ポインタが配置される位置へのポインタ。
vm_args
: Java VM初期化引数。
成功した場合はJNI_OK
を返し、失敗した場合は該当するJNIエラー・コード(負の数)を返します。
jint DestroyJavaVM(JavaVM *vm);
Java VMをアンロードし、そのリソースを回復します。
どのスレッドでも、接続されているかどうかにかかわらず、この関数を呼び出すことができます。現在のスレッドが接続されている場合、VMは、現在のスレッドがデーモンでない唯一のユーザー・レベルJavaスレッドになるまで待機します。現在のスレッドが接続されていない場合は、VMが現在のスレッドを接続し、現在のスレッドがデーモンでない唯一のユーザー・レベルのスレッドになるまで待機します。
JavaVMインタフェース関数表のインデックス3。
vm
: 破棄されるJava VM。
成功した場合はJNI_OK
を返し、失敗した場合は該当するJNIエラー・コード(負の数)を返します。
VMのアンロードはサポートされていません。
jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
現在のスレッドをJava VMへ接続します。JNIEnv
引数でJNIインタフェース・ポインタを返します。
すでに接続されているスレッドへの接続は、無操作です。
ネイティブ・スレッドを2つのJava VMへ同時に接続することはできません。
スレッドがVMに接続されている場合、コンテキスト・クラスのローダーは、ブートストラップ・ローダーです。
JavaVMインタフェース関数表のインデックス4。
vm
: 現在のスレッドが接続されるVM。
p_env
: 現在のスレッドのJNIインタフェース・ポインタが配置される位置へのポインタ。
thr_args
: NULLまたはJavaVMAttachArgs
構造体を参照するポインタにして、追加情報を指定できます。
AttachCurrentThread
の2番目の引数は、常にJNIEnv
へのポインタです。AttachCurrentThread
への3番目の引数は予約されており、NULL
に設定しなければなりません。
追加情報を指定するには、次の構造体にポインタを渡します。
typedef struct JavaVMAttachArgs { jint version; char *name; /* the name of the thread as a modified UTF-8 string, or NULL */ jobject group; /* global ref of a ThreadGroup object, or NULL */ } JavaVMAttachArgs
成功した場合はJNI_OK
を返し、失敗した場合は該当するJNIエラー・コード(負の数)を返します。
jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args);
AttachCurrentThreadとセマンティックスは同じですが、新しく作成されたjava.lang.Threadインスタンスはデーモンです。
スレッドがすでにAttachCurrentThreadまたはAttachCurrentThreadAsDaemonを介して接続されている場合、このルーチンは、penvが指している値を現在のスレッドのJNIEnvに設定します。この場合、AttachCurrentThreadもこのルーチンも、スレッドのデーモン・ステータスに影響しません。
JavaVMインタフェース関数表のインデックス7。
vm: 現在のスレッドが接続される仮想マシン・インスタンス。
penv: 現在のスレッドのJNIEnvインタフェース・ポインタが配置される位置へのポインタ。
args: JavaVMAttachArgs構造体へのポインタ。
成功した場合はJNI_OK
を返し、失敗した場合は該当するJNIエラー・コード(負の数)を返します。
なし。
jint DetachCurrentThread(JavaVM *vm);
Java VMから現在のスレッドを分離します。このスレッドが保持するJavaモニターはすべて解放されます。このスレッドが終了するのを待つJavaスレッドすべてに、通知が行われます。
VMからメイン・スレッドを切り離すことができます。JavaVMインタフェース関数表のインデックス5。
vm
: 現在のスレッドが分離されるVM。
成功した場合はJNI_OK
を返し、失敗した場合は該当するJNIエラー・コード(負の数)を返します。
jint GetEnv(JavaVM *vm, void **env, jint version);
JavaVMインタフェース関数表のインデックス6。
vm
: インタフェース取得元の仮想マシン・インスタンス。env
: 現在のスレッドのJNIインタフェース・ポインタが配置される位置へのポインタ。version
: 要求されたJNIバージョン。現在のスレッドがVMに接続されていない場合は、*env
をNULL
に設定し、JNI_EDETACHED
を返します。指定されたバージョンがサポートされていない場合は、*env
をNULL
に設定し、JNI_EVERSION
を返します。それ以外の場合は、*env
を適切なインタフェースに設定し、JNI_OK
を返します。
目次|前 |