JNI (Java Native Interface)は、Java仮想マシンとネイティブアプリケーションを組み合わせるための標準プログラミングインタフェースである。
Javaから呼び出されているネイティブライブラリをデバッガで動作させるには、環境変数DEBUG_PROGにデバッガを定義します。そしてJavaを実行させるとデバッガが起動されますので、stop dlopenコマンドでデバッグするライブラリのパスを指定します。runコマンドで実行すると、libxxx.soが呼び出されると停止するので、ブレークポイントを設定して実行を継続します。
$ setenv DEBUG_PROG dbx
$ java
(dbx) stop dlopenn ライブラリのパス
(dbx) run Test.Test_0
(dbx) file operate.c
(dbx) stop at 行番号
(dbx) cont
JNIを使うと、ネイティブ・コード(C言語やC++など)からJavaのクラスを利用したり、メソッドを呼ぶことができる。
C言語のサンプル・コードを次に示す。
include "jni.h"
int main()
{
JNIEnv *env;
JavaVM *jvm;
int res;
jclass clazz;
jmethodID mid;
JavaVMOption options[3];
options[0].optionString = "-Xmx128m";
options[1].optionString = "-verbose:gc";
options[2].optionString = "-Djava.class.path=/home/yajima/class";
JavaVMInitArgs vm_args;
vm_args.version = JNI_VERSION_1_6;
vm_args.options = options;
vm_args.nOptions = 3;
/* Java VM の作成 */
res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
if (res < 0) {
fprintf(stderr, "JNI_CreateJavaVM error\n");
return -1;
}
/* Javaクラスの検索 */
clazz = (*env)->FindClass(env, "Hello");
if (clazz == 0) {
fprintf(stderr, "FindClass error\n");
return -1;
}
/* メソッド識別子の取得 */
mid = (*env)->GetStaticMethodID(env, clazz, "main",
"([Ljava/lang/String;)V");
if (mid == 0) {
fprintf(stderr, "GetStaticMethodID error\n");
return -1;
}
/* Hello#main の呼び出し */
(*env)->CallStaticVoidMethod(env, clazz, mid, NULL);
/* Java VM の破棄 */
(*jvm)->DestroyJavaVM(jvm);
return 0;
}
C言語から呼び出されるJavaのサンプル・コードを次に示す。
public class Hello {
public static void main(String args[]) {
System.out.println("Hello!");
}
}
C言語/C++ のプログラムで、ヘッダファイル jni.h をインクルードする必要がある。jni.h は JDK_HOME/include
ディレクトリに含まれているので、このディレクトリをインクルードファイルのディレクトリとしてコンパイラに指定する。
jni.h の中では jni_md.h をインクルードしている。jni_md.h にはプラットフォームに依存する内容が含まれている。そのため、jni_md.h が存在するディレクトリはプラットフォームにより異なる。このディレクトリもインクルードファイルのディレクトリとしてコンパイラに指定する。
jni_md.h が存在するディレクトリの一例を次に示す。
OS | パス |
---|---|
Windows | include\win32 |
Linux | include/linux |
Solaris | include/solaris |
Javaとネイティブ変数型(C言語)のマッピングを次の表に示す。
Java変数型 | ネイティブ変数型 |
---|---|
jboolean | unsigned char |
jbyte | signed char |
jchar | unsigned short |
jshort | short |
jnit | int |
jlong | long long __int64 |
jfloat | float |
jdouble | double |
Java VMをロードして初期化するには、JNI_CreateJavaVM()関数を呼び出す。
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
引数pvmには、呼び出しAPI関数テーブルのポインタ (JavaVM *) を格納する領域へのポインタを渡す。
呼び出しAPI関数テーブルには、次に示す関数へのポインタが格納される。
引数argsには、JavaVMInitArgs構造体を参照するポインタを指定する。
JavaVM
は、JNIInvokeInterface_
構造体へのポインタ型である。
#ifdef __cplusplus
typedef JavaVM_ JavaVM;
#else
typedef const struct JNIInvokeInterface_ *JavaVM;
#endif
JNIInvokeInterface_
は、呼び出しAPI関数テーブルを格納する構造体である。
struct JNIInvokeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2;
jint (JNICALL *DestroyJavaVM)(JavaVM *vm);
jint (JNICALL *AttachCurrentThread)
(JavaVM *vm, void **penv, void *args);
jint (JNICALL *DetachCurrentThread)
(JavaVM *vm);
jint (JNICALL *GetEnv)
(JavaVM *vm, void **penv, jint version);
jint (JNICALL *AttachCurrentThreadAsDaemon)
(JavaVM *vm, void **penv, void *args);
};
JavaVMInitArgsは、Java VMの起動オプションを格納するための構造体である。
typedef struct JavaVMInitArgs {
jint version;
jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;
JNI_TRUE
を指定すると、認識できないオプション文字列があっても無視される。JNI_FALSE
を指定すると、認識できないオプション文字列があったらJNI_ERR
を返す。各VM実装は、標準ではない独自のオプション文字列をサポートしている。標準でないオプション名は、-X
または _
で始まる。使用するVMの選択によっては、これらのオプションを認識できないことがある。Javaのクラスを検索するには、インタフェース関数テーブル (JNIEnv) を介してFindClassを呼び出す。
jclass (JNICALL *FindClass)(JNIEnv *env, const char *name);
引数nameには、クラス名を指定する。
Javaはメソッドのオーバーロードができるため、メソッド名だけではメソッドを特定できない。特定のメソッドを識別するための識別子がメソッド識別子である。メソッド識別子の型は jmethodID
である。メソッドを呼び出す前に、呼び出すメソッドを特定するメソッド識別子を取得する必要がある。
スタティック・メソッド識別子を取得するには、インタフェース関数テーブル (JNIEnv) を介してGetStaticMethodIDを呼び出す。
jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass clazz, const char *name, const char *sig);
引数nameにはメソッド名を指定する。
引数sigにはメソッドのシグネチャを指定する。シグネチャは、メソッドの引数リストと戻り値の型を記述した文字列で指定する。シグネチャの指定形式を次に示す。
(引数型のリスト)戻り値の型
引数型のリストには引数の型を指定する。引数が複数ある場合は、引数の型を複数並べる。複数並べる場合、区切り文字は必要ない。
戻り値の型には戻り値の型を指定する。
引数の型や戻り値の型を表す文字列を次に示す。
型のシグネチャ | Javaの型 |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
L クラスの完全修飾名; | 完全指定のクラス |
[ 型のシグネチャ | 型 [] (型の配列) |
V | void |
たとえば、long foo(int n, String s, int[] arr);
というJavaメソッドのシグネチャは (ILjava/lang/String;[I)J
となる。シグネチャを細かく分けてみると、次の通りとなる。
シグネチャ | 備考 |
---|---|
( | 引数型リストの始まり |
I | int n |
Ljava/lang/String; | String s |
[I | int[] arr |
) | 引数型リストの終わり |
J | long (戻り値の型) |
同じメソッド名で引数リストが異なるメソッドを複数定義することをメソッドのオーバーロードと呼ぶ。同じメソッド名でも引数リストが異なるものは、それぞれ別のメソッドとして扱われる。
このため、Javaではメソッドを識別するのにメソッド名と引数リストの組を使う。これをシグネチャ(署名)と呼ぶ。
staticメソッドを呼び出すには、インタフェース関数テーブル (JNIEnv) を介してJNI関数を呼び出す。呼び出すJNI関数は、戻り値の型およびメソッドへの引数の渡し方によって複数用意されている。
メソッドに渡す引数をすべて羅列して渡す。
メソッドに渡す引数をjvalue型の配列に格納して渡す。
メソッドに渡す引数をva_list型で渡す。
jboolean (JNICALL *CallStaticBooleanMethod)(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
jboolean (JNICALL *CallStaticBooleanMethodV)(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
jboolean (JNICALL *CallStaticBooleanMethodA)(
JNIEnv *env,
jclass clazz,
jmethodID methodID,
const jvalue *args
);
jint (JNICALL *DestroyJavaVM)(JavaVM *vm);
ネイティブ・コードはJVM (Java VM) ライブラリとリンクする必要がある。JVMライブラリはJRE (Java Runtime Environment) に含まれている。用途やプラットフォームごとに複数のJVMライブラリが存在するので、どれかひとつを選んでリンクする。
Microsoft Windows における JVM ライブラリの一例を以下に示す。
パス | 備考 |
---|---|
lib\jvm.lib | ライブラリ |
jre\bin\client\jvm.dll | Java HotSpot Client VM DLL |
jre\bin\server\jvm.dll | Java HotSpot Server VM DLL |
Solaris における JVM ライブラリの一覧を以下に示す。
パス | 備考 |
---|---|
jre/lib/sparc/client/libjvm.so | 32ビット SPARC Java HotSpot Client VM |
jre/lib/sparc/server/libjvm.so | 32ビット SPARC Java HotSpot Server VM |
jre/lib/sparcv9/server/libjvm.so | 64ビット SPARC Java HotSpot Server VM |
jre/lib/i386/client/libjvm.so | x86 Java HotSpot Client VM |
jre/lib/i386/server/libjvm.so | x86 Java HotSpot Server VM |
jre/lib/x64/server/libjvm.so | x64/EMT64 Java HotSpot Server VM |