Java Native Interface (JNI)

JNI (Java Native Interface)は、Java仮想マシンとネイティブアプリケーションを組み合わせるための標準プログラミングインタフェースである。

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言語からJavaのメソッドを呼ぶ

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!");
  }
}

jni.hのインクルード

C言語/C++ のプログラムで、ヘッダファイル jni.h をインクルードする必要がある。jni.h は JDK_HOME/include ディレクトリに含まれているので、このディレクトリをインクルードファイルのディレクトリとしてコンパイラに指定する。

jni.h の中では jni_md.h をインクルードしている。jni_md.h にはプラットフォームに依存する内容が含まれている。そのため、jni_md.h が存在するディレクトリはプラットフォームにより異なる。このディレクトリもインクルードファイルのディレクトリとしてコンパイラに指定する。

jni_md.h が存在するディレクトリの一例を次に示す。

jni_md.hのディレクトリ
OSパス
Windowsinclude\win32
Linuxinclude/linux
Solarisinclude/solaris

JNI変数型のマッピング

Javaとネイティブ変数型(C言語)のマッピングを次の表に示す。

JNI変数型のマッピング
Java変数型ネイティブ変数型
jbooleanunsigned char
jbytesigned char
jcharunsigned short
jshortshort
jnitint
jlonglong long __int64
jfloatfloat
jdoubledouble

Java VMの生成

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型

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構造体

JavaVMInitArgsは、Java VMの起動オプションを格納するための構造体である。

typedef struct JavaVMInitArgs {
  jint version;

  jint nOptions;
  JavaVMOption *options;
  jboolean ignoreUnrecognized;
} JavaVMInitArgs;
version
JNIのバージョン
options
JavaVMOption構造体の配列
nOptions
JavaVMOption構造体配列の要素数
ignoreUnrecognized
認識できないオプション文字列が指定されたときの動作を指定する。JNI_TRUEを指定すると、認識できないオプション文字列があっても無視される。JNI_FALSEを指定すると、認識できないオプション文字列があったらJNI_ERRを返す。各VM実装は、標準ではない独自のオプション文字列をサポートしている。標準でないオプション名は、-X または _ で始まる。使用するVMの選択によっては、これらのオプションを認識できないことがある。

Javaクラスの検索

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の型
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
Lクラスの完全修飾名;完全指定のクラス
[型のシグネチャ []の配列)
Vvoid

たとえば、long foo(int n, String s, int[] arr); というJavaメソッドのシグネチャは (ILjava/lang/String;[I)J となる。シグネチャを細かく分けてみると、次の通りとなる。

シグネチャ備考
(引数型リストの始まり
Iint n
Ljava/lang/String;String s
[Iint[] arr
)引数型リストの終わり
Jlong(戻り値の型)

シグネチャ

同じメソッド名で引数リストが異なるメソッドを複数定義することをメソッドのオーバーロードと呼ぶ。同じメソッド名でも引数リストが異なるものは、それぞれ別のメソッドとして扱われる。

このため、Javaではメソッドを識別するのにメソッド名と引数リストの組を使う。これをシグネチャ(署名)と呼ぶ。

staticメソッドの呼び出し

staticメソッドを呼び出すには、インタフェース関数テーブル (JNIEnv) を介してJNI関数を呼び出す。呼び出すJNI関数は、戻り値の型およびメソッドへの引数の渡し方によって複数用意されている。

CallStatic<type>Method

メソッドに渡す引数をすべて羅列して渡す。

CallStatic<type>MethodA

メソッドに渡す引数をjvalue型の配列に格納して渡す。

CallStatic<type>MethodV

メソッドに渡す引数を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
);

Java VMのアンロード

jint (JNICALL *DestroyJavaVM)(JavaVM *vm);

ネイティブ・コードはJVM (Java VM) ライブラリとリンクする必要がある。JVMライブラリはJRE (Java Runtime Environment) に含まれている。用途やプラットフォームごとに複数のJVMライブラリが存在するので、どれかひとつを選んでリンクする。

Microsoft Windows における JVM ライブラリの一例を以下に示す。

Windows の JVM ライブラリ
パス備考
lib\jvm.libライブラリ
jre\bin\client\jvm.dllJava HotSpot Client VM DLL
jre\bin\server\jvm.dllJava HotSpot Server VM DLL

Solaris における JVM ライブラリの一覧を以下に示す。

Solaris の JVM ライブラリ
パス備考
jre/lib/sparc/client/libjvm.so32ビット SPARC Java HotSpot Client VM
jre/lib/sparc/server/libjvm.so32ビット SPARC Java HotSpot Server VM
jre/lib/sparcv9/server/libjvm.so64ビット SPARC Java HotSpot Server VM
jre/lib/i386/client/libjvm.sox86 Java HotSpot Client VM
jre/lib/i386/server/libjvm.sox86 Java HotSpot Server VM
jre/lib/x64/server/libjvm.sox64/EMT64 Java HotSpot Server VM