とてもくだらないことで何時間も費やしてしまった…。いろいろ調べてるうちに誤情報とかもあって混乱したので、JNI 呼び出しの気をつけることをまとめてます。
●Java 側の定義
定義は以下のような形で行います。
package com.sample;
class MyClass {
public native void my_method( int arg );
}
C 側の関数の実体を定義していなくてもコンパイルは通ります。これは実行時に、現在読み込んでいる実行モジュール (.so) から関数を探すようになっているためで、コンパイルが通ったからといって呼び出しが成功するとは限りません。
●C 側の関数名は自分で書かずに javah を使用する
Java から呼び出す関数名の定義にはいくつか約束事があり、それを全部理解しているならいいのですが、いろいろわかりにくいため javah でプロトタイプ宣言を自動作成するのが一番確実です。
例えば、'_'(アンダーバー) は JNI ではパッケージ名の区切り記号として使用するため、関数名にアンダーバーが含まれる場合には特殊な置き換えが必要になります。
また、クラスにメンバ変数があるかどうかによって、関数の第2引数の型が変化するようです。(jobject だったり jclass だったり)
これらの細かい仕様は javah でヘッダを出力するようにすればすべて自動でやってくれます。それから、初心者の方がよくはまりがちな extern "C" もちゃんと一緒に出力してくれます。
javah -classpath <パス名> <クラス名>
<パス名>はコンパイル済みの Java のクラスのあるパスを指定します。eclipse でコンパイルしているなら「プロジェクトのパス/bin/classes」になります。
<クラス名>はパッケージ名も含めたフルクラス名です。上の例なら com.sample.MyClass になります。
●Java 側で JNI モジュールを読み込む
JNI メソッドを呼び出すより前に、必ず JNI モジュールを読み込んでいる必要があります。ほとんどの場合は、起動と同時に読み込むことになると思うので、起動時に実行するクラスで、
class Main
{
static {
System.loadLibrary( "MyModule" );
}
}
と書くことになります。
注意点としては、読み込むモジュールのファイル名は必ず "lib???.so" というファイル名である必要があり、loadLibrary で指定する名前は、このファイル名から "lib" と ".so" を除いた部分であるという点です。上の例なら、"libMyModule.so" というファイルが読み込まれます。