ふと、Java からネイティブコードの呼び出し方法を調べてみました。
今回はネイティブコードとして、Windows の DLL、Linux の so、Mac の jnilib を Call する方法を調査してみます。
Java には標準で JNI(Java Native Interface)という機能があり、Java で開発されたプログラムから他の言語で開発されたネイティブコードを利用するための API として利用できます。
開発環境は以下の通りで、dll は Windows じゃないですが Cygwin 上で作成することにしました。
boland の C++ はユーザー登録が面倒なのでやめました。
Eclipse3.2.0(ビルドはAntを使用)
jdk1.5.0_13
Cygwin(gcc)
Linux(gcc)
MacOS(gcc)
Javaプログラムの作成
まずは、ネイティブメソッドを含む Java プログラムを作成してコンパイルします。
今回は Ant で定義しましたが、コマンドラインでビルドするときは下記のようにします。
1 | $ javac sample.java |
次に、生成されたクラスファイルからヘッダファイルを作成します。
こちらも、コマンドラインでの生成方法を書いておきます。
1 | $ javah -jni [クラスファイル名(.classは不要)] |
上記で生成したヘッダファイルを参照して C++ のプログラムを作成します。
今回は、クラス名が sample、Java のネイティブメソッドを getWord とします。
1 2 3 4 5 6 | [ネイティブ関数の定義] JNIEXPORT jstring JNICALL Java_sample_getWord (JNIEnv *env, jobject obj, jstring str1, jstring str2 ) { [ネイティブ関数の命名規約] Java_[パッケージ名]_[クラスファイル名]_[ネイティブ関数名] 引数は、左から |
1 2 3 4 5 6 7 8 | [JNI インタフェースポインタ(JNIEnv型)] すべての JNI 関数のポインタを格納する構造体を指すポインタ [オブジェクトの参照] static のネイティブメソッドの場合は、Java クラスの参照 [Java 側で指定した引数(1番目)] ネイティブ型 [Java 側で指定した引数(2番目)] ネイティブ型 |
最後に、作成した C++ プログラムから共有ライブラリを作成します。
so や jnilib ファイルは、ファイル名の先頭に lib を付けます。
1 2 3 | [dll] sample.dll [so] libsample.so [jnilib] libsample.jnilib |
次は Java や C++ のソースも交えて、それぞれのプラットフォームでのライブラリの作成方法をまとめたいと思います。
Cygwin, Linux, MacOS それぞれでビルド方法も違うので苦労しました・・・。
ライブラリの作成
今回は実際にライブラリの作成をしてみます。
最初は、Cygwin 上で gcc を使って dll を作成します。
早速の注意事項として、${JAVA_HOME}\include\win32\jni_md.h の型マッピングの定義が一致しないので対応しないといけません。
対応方法は、gcc 実行時に引数として -D__int64=’long long’ を定義する方法と、jni_md.h の jlong の定義を直接書き換える方法があります。
1 2 3 4 5 6 7 8 | (例)-D__int64='long long' を定義する方法 gcc -D__int64='long long' (例)jni_md.h の jlong の定義を書き換える方法 #ifdef __GNUC__ typedef long long jlong; #else typedef __int64 jlong; #endif |
まずはコンパイルで sample.o ファイルを生成します。
gcc の -m はコンフィグ依存のオプションで、 no-cygwin によって Cygwin DLL とリンクしないように指定しています。
1 2 3 4 | gcc -c -mno-cygwin -shared -D__int64='long long' \ -I"C:/Program Files/Java/jdk1.5.0_13/include" \ -I"C:/Program Files/Java/jdk1.5.0_13/include/win32" \ sample.cpp |
sample.o ができたら、さらにコンパイルして dll を生成します。
1 2 | dllwrap -mno-cygwin --add-stdcall-alias -mwindows --target=i386-mingw32 \ -o sample.dll -s sample.o |
これで、dll の作成ができたので早速 Java プログラムを実行してみます。
コマンドラインからの実行方法は、java [クラス名] です。
次は Linux と Mac で使用するライブラリの作成方法についてまとめます。
LinuxとMacOS上でライブラリ作成
今回は、Linux と MacOS 上でライブラリを作成します。
まずは、オブジェクト(.oファイル)を作成します。
1 2 3 | $ gcc -fPIC -g -I${JAVA_HOME}/include \ -I${JAVA_HOME}/include/linux \ -c sample.cpp |
続いて、Linux の動的共有オブジェクト(.soファイル)を作成します。
1 | $ gcc -lstdc++ -shared -o libsample.so sample.o |
Linux 上で、JDK1.5.0_13(Update13)を使用してネイティブコードを実行すると下記のエラーが出ます。
1 2 3 4 5 6 7 8 9 | # java sample Exception in thread "main" java.lang.UnsatisfiedLinkError: /usr/local/jdk1.5.0_13/jre/lib/i386/libsample.so: Can't load IA 32-bit .so on a IA 32-bit platform at java.lang.ClassLoader$NativeLibrary.load(Native Method) at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1751) at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1668) at java.lang.Runtime.loadLibrary0(Runtime.java:822) at java.lang.System.loadLibrary(System.java:993) at sample.<clinit>(sample.java:6) |
そこで、JDK1.5.0_09(Update9)にバージョンダウンしてみます。
古いバージョンのモジュールは「古いJDKのモジュールを探す」を参考にして下さい。

詳細はまだわかりませんが、JDK1.5.0_10(Update10)から内部的に何かが変わったみたいです。
わかり次第お伝えしようと思います。
JDK1.5.0_09(Update9)では問題なく実行が可能です。
次に、MacOS 上でのライブラリの作成です。Linux 同様に、オブジェクト(.oファイル)を作成します。
1 | $ gcc -fPIC -g -I${JAVA_HOME}/include -c sample.cpp |
続いて、動的共有オブジェクト(.jnilibファイル)を作成します。MacOS では jnilib というファイルにしないと実現できないようです。
1 | $ gcc -lstdc++ -bundle -o libsample.jnilib sample.o |
あとは、java コマンドで実行すると動作が確認できます。
Javaのサンプルプログラム
実際に作った Java のサンプルプログラムとヘッダファイルの紹介です。
今回は、下記の 4 つのメソッドを実装しています。
1 2 3 4 | 1.C++ 側で生成した文字列を表示する 2.引数で渡した 2 つの文字列を連結して表示する 3.引数で渡した数値に適当に数値を足して表示する 4.引数で渡したバイト配列から部分的にバイト文字を置換して表示する |
Java のソースコードは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class JniSample { // JNIモジュールのロード static { try { System.loadLibrary("JniSample"); } catch (Exception e) { System.out.println(e.getStackTrace()); } } // ネイティブメソッド native public String getWord(); native public String getWordParam(String str1, String str2); native public String getNumber(int num); native public void getByteWord(byte arrByte[]); // メイン public static void main(String[] args) throws Exception { // インスタンス JniSample jniSample = new JniSample(); // getWord()呼び出し System.out.println(jniSample.getWord()); // getWordParam()呼び出し String msg = jniSample.getWordParam("ABCDE", "FGHIJ"); System.out.println(msg); // getNumber()呼び出し System.out.println(jniSample.getNumber(5)); // getByteWord()呼び出し byte arr[] = new byte[5]; StringBuffer sb = new StringBuffer(""); for (int i = 0; i < arr.length; i ++) { arr[i] = (byte)(i + 1); } jniSample.getByteWord(arr); for (int i = 0; i < arr.length; i ++) { sb.append(arr[i]); } System.out.println(sb.toString()); } } |
javah で生成したヘッダファイルは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class JniSample */ #ifndef _Included_JniSample #define _Included_JniSample #ifdef __cplusplus extern "C" { #endif /* * Class: JniSample * Method: getWord * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_JniSample_getWord (JNIEnv *, jobject); /* * Class: JniSample * Method: getWordParam * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_JniSample_getWordParam (JNIEnv *, jobject, jstring, jstring); /* * Class: JniSample * Method: getNumber * Signature: (I)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_JniSample_getNumber (JNIEnv *, jobject, jint); /* * Class: JniSample * Method: getByteWord * Signature: ([B)V */ JNIEXPORT void JNICALL Java_JniSample_getByteWord (JNIEnv *, jobject, jbyteArray); #ifdef __cplusplus } #endif #endif |
C++でプログラムを作成
今回は、C++ で作成したプログラムです。
(コメントは省いてます)
何か文字列を出力する簡単なサンプルを作ると、ついつい「Hello World」って書いちゃうのはなんででしょうか(笑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <stdio.h> #include <string.h> #include <jni.h> #include "JniSample.h" JNIEXPORT jstring JNICALL Java_JniSample_getWord (JNIEnv *env, jobject obj ) { return env->NewStringUTF("Hello World"); } JNIEXPORT jstring JNICALL Java_JniSample_getWordParam (JNIEnv *env, jobject obj, jstring str1, jstring str2 ) { char buf[256]; const char *c1 = env->GetStringUTFChars(str1, 0); const char *c2 = env->GetStringUTFChars(str2, 0); strcpy(buf, c1); strcat(buf, c2); env->ReleaseStringUTFChars(str1, c1); env->ReleaseStringUTFChars(str2, c2); return env->NewStringUTF(buf); } JNIEXPORT jstring JNICALL Java_JniSample_getNumber (JNIEnv *env, jobject obj, jint num ) { char buf[256]; for (int i = 0; i < 10; i++) { num = num + i; } sprintf(buf, "%d", num); return env->NewStringUTF(buf); } JNIEXPORT void JNICALL Java_JniSample_getByteWord (JNIEnv *env, jobject obj, jbyteArray arrByte) { jboolean b; jbyte* arrData = env->GetByteArrayElements(arrByte, &b); arrData[0] = 5; env->ReleaseByteArrayElements(arrByte, arrData, 0); } |