メモの日々


2012年08月19日(日) [長年日記]

  • 古江が大企業の幹部になっている夢をみた。

[java] JNIを使う

JNIを使ってみたので手順をメモ。Windows上で、MinGW-w64を使ってネイティブコードの実装を行う。

  1. ネイティブメソッドを持つクラスの作成 (Java)
  2. ヘッダファイルの生成 (Java)
  3. ネイティブメソッドの実装 (C++)
  4. 共有ライブラリの作成 (C++)
  5. 実行 (Java)

ネイティブメソッドを持つクラスの作成 (Java)

package sample;

public class NativeCounter {
    private int value = 0;
    public native int add(int i); // これをC++で実装する。

    public static void main(String[] args){
        NativeCounter counter = new NativeCounter();
        counter.add(100);
        System.out.println(counter.add(100));
    }

    // ライブラリのロード処理。
    static {
        System.loadLibrary("counter");
    }
}

add()がネイティブメソッド。native修飾子を付けて、実装はなしにする。

ネイティブメソッドを使えるようにするには後で作成する共有ライブラリをロードする必要があるので、System.loadLibrary()によりそれも行っている。

ヘッダファイルの生成 (Java)

javahコマンドを使うと、ネイティブメソッドの実装に必要なヘッダファイルを生成できる。

> javah -cp . sample.NativeCounter

これでカレントディレクトリに sample_NativeCounter.h が生成された。なお、javahの -stubs オプションは機能しないみたい。

ネイティブメソッドの実装 (C++)

生成された sample_NativeCounter.h に合わせて sample_NativeCounter.cpp を次のように書いてみる。

#include "sample_NativeCounter.h"

JNIEXPORT jint JNICALL Java_sample_NativeCounter_add(
        JNIEnv *env,
        jobject obj, // JavaのNativeCounterオブジェクトを指す
        jint i) {
    // NativeCounter#valueフィールドを取得
    jclass objClass = env->GetObjectClass(obj);
    jfieldID valueField = env->GetFieldID(objClass, "value", "I");

    // フィールドを取得できない場合は例外が投げられているはず。速やかに
    // returnするとJava側では戻り値ではなく例外を受け取る。
    if (!valueField) return 0;

    // NativeCounter#valueフィールドの値の取得と更新
    jint v = env->GetIntField(obj, valueField);
    jint result = v + i;
    env->SetIntField(obj, valueField, result);

    return result;
}

ネイティブコード内でのJavaとのやりとりはJNIEnvクラスが持つメンバ関数を介して行える。使える関数はここで分かる。

手元の環境だとjint型はlongのtypedefだった。

共有ライブラリの作成 (C++)

上述の通り、MinGW-w64のg++を使って共有ライブラリを作成する。MinGW-w64ではなくMinGWの方のウェブサイトにJNI-MinGW-DLLというページがあったので、ここに従って

> g++ -Wall -D_JNI_IMPLEMENTATION_ -Wl,--kill-at ^
  -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" ^
  -shared -o counter.dll sample_NativeCounter.cpp

とした(コマンドプロンプトで実行)。_JNI_IMPLEMENTATION_ の定義の必要性は理解していない。jni.h内で使われているが、定義しなくても動作した。-Wl,--kill-at は必要で、これがないとJavaからDLL内の関数を見つけられなかった。

実行 (Java)

作成したNativeCounterクラスの実行時には、上で作った counter.dll をロードできるようにする必要がある。実行時にシステムプロパティの java.library.path に counter.dll が置かれているディレクトリを指定するのが一つの方法。

> java -cp . -Djava.library.path=lib sample.NativeCounter
200