目次
Java native Interfaceだいぶ前に使ったが、すっかりわすれていて久し振りに使いたくなったので ついでに忘れてもいいようにここにもメモしとこうと思いました φ(。。JDK6のドキュメントに含まれているJNIのドキュメントの日付は2003年となっているので、 新しいことは無いと思われるが、一応一通りRHEL-as4u4/jdk6u1でやってみる。
Java2nativeまずは Javaクラスメソッドの実体をnativeで実装する場合からスタート。 このパターンは 順当にクラスメソッドの実体をnativeで実装することになる。 従って作業は以下の通り。
さんぷる基本の printf("Hello JNI World\n")だろうwと言うことで、こんな感じ。
1. クラスをつくるまぁ、まんま。doItメソッドのmethod sigtatureに JNIのために定義されている nativeをつける必要がある。ついでに動かすためのmainもつけとく。package info.imap4rev1 public class Main { native int doIt(String s); public static void main(String [] args) { Main m = new Main(); m.doIt(args.length > 0 ? args[0] : null); } } 2. libraryをloadするmethodの本体となるnativeの実装となるlibraryは、staticブロックに含めて、JVMがclassをloadする際に 併せてlibraryをdynamic loadするように書いておく。今回のshared libraryの名称は libtest.soにしよう。package info.imap4rev1 public class Main { public native int doIt(String s); public static void main(String [] args) { Main m = new Main(); m.doIt(args.length > 0 ? args[0] : null); } static { System.loadLibaray("test"); } } 3. メソッドの実装をnativeでつくるまず実装する場合の関数名だが、以下のnaming ruleに従う。
こういうのは実際のコードを見るのが一番分り易いと思う。何年か昔だったら、J2MEの Linux向けriが配付されていたので、それを見れと言うところだが、今の時分では opekjdkなる 時代になったので、jdk6/jdk7(=OpenJDK)のソースを直接見て、Sunが提供しているJDKの 直接のソースをこの辺から見ることが可能である。 いい時代になったねぇ。 ま、よた話はおいといて、今回の場合のmethodに対応する関数名は以下の通りになる。
で、引数String、戻り値intに対応するJNIの型が jstring/jintとなるので、 実装する関数は、文字列の取りだし方は色々あるが、例えばこうなる。 JNIEXPORT jint JNICALL Java_info_imap4rev1_Main_doIt( JNIEnv *env, // interface pointer jobject this, // "this" pointer jstring s // arg #1 ) { jint i = 0; jboolean isCopy; char *p; if ( s != NULL && (*env)->GetStringLength(env, s) > 0 ) { p = (*env)->GetStringUTFChars(env, s, &isCopy); printf("Hello %s World\n", p); } else { i = -1; } return i; }ここではStringと言うjava.langパッケージで提供されていると言うか、JNIにも型として 存在するクラスを引数にしているので、方法が色々あるし、取りだしも簡単になっている。 無論、メソッドやフィールドにアクセスする方法が提供されていて、reflectionみたいな 感じでアクセスするi/fが提供されている。(後述するかも)
マクロの目的関数名の前後にJNIEXPORTとJNICALLというマクロが (javahコマンドで生成されるヘッダファイルのプロトタイプに)貼ってある。jdk6のソースツリーから考えると JNIEXPORT/JNICALL共にLinux環境では 書いていなくても問題ない。(Windowsの場合、このマクロに意味がある)
4. 作った実装をshared libraryにする実装は3.のでいいが、肝心の書きはじめがガイドには無いw仕方がないので作り方の手順もここに含めておく。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class info_imap4rev1_Main */ #ifndef _Included_info_imap4rev1_Main #define _Included_info_imap4rev1_Main #ifdef __cplusplus extern "C" { #endif /* * Class: info_imap4rev1_Main * Method: doIt * Signature: (Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_info_imap4rev1_Main_doIt (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif
実行結果csh -c "setenv LD_LIBRARY_PATH .; java -classpath . info.imap4rev1.Main hoge" Hello hoge Worldまぁ順当。文字列の抜きだしが GetStringChars?()だとばらばらに抜けちゃうので、 GetStringUTFChars()を使うように。 あと、segvった場合、hs_err_xxxxファイルに色々ダンプされる。stackを見れば nativeコードの何番地でsegvったか判るので libraryを objdump -S してやれば デバッグに役立つ。なので安心してcrashするのをがしがし書いてみようw
native2Java続いて、nativeからJavaクラスを利用する場合。今度はJava2nativeで作ったMainクラスをロードして Main.doIt()を 呼び出してみる。作業は1個のnative実装を作るだけで、中でやることは下記の通り。
さんぷる動作仕様はこんな感じでいいだろう。
途中でめんどうになったので、見れば判る通り例外処理を大量に端折ってます。 ちゃんと書くなら NULL checkとかbounds checkはちゃんと書きますよ?w
#include <jni.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> /* typedef struct JavaVMInitArgs { jint version; jint nOption; JavaVMOption *options; jboolean ignoreUnrecognized; }JavaVMInitargs; typedef struct JavaVMOption { char *optionString; void *extraInfo; } JavaVMOption; */ jstring getString(JNIEnv *env, char *p) { jstring str = NULL; int plen; jclass sclz; jmethodID mid; jbyteArray array; plen = strlen(p); if ( (sclz = (*env)->FindClass(env, "java/lang/String")) == NULL ) { return NULL; } // ここでは String(Byte [])のコンストラクタを使うことにしたので // p/f defaultのencoding込みで入力文字列はUnicodeとして保持される if ( (mid = (*env)->GetMethodID(env, sclz, "<init>", "([B)V")) == NULL ) { return NULL; } // ここで作った arrayはmallocしているので、この関数から戻る前に // free(=DeleteLocalRef)しないとmemory leakになる if ( (array = (*env)->NewByteArray(env, plen)) == NULL ) { return NULL; } (*env)->SetByteArrayRegion(env, array, 0, plen, (jbyte *)p); if ( (*env)->ExceptionOccurred(env) ) { return NULL; } // ここのJNI関数NewObject()が new String(byte [])の呼び出しに対応する str = (*env)->NewObject(env, sclz, mid, array); // 使ったのを片付け (*env)->DeleteLocalRef(env, array); return str; } int doIt(char *p) { JavaVM *jvm; JNIEnv *env; JavaVMInitArgs vm_args; JavaVMInitArgs *vap = &vm_args; int ret; jclass clz; jmethodID mid; jstring js; jobject mobj; // JVMの初期化。ここはテンプラだ vap->version = JNI_VERSION_1_6; if ( (ret = JNI_GetDefaultJavaVMInitArgs(vap)) < 0 ) { fprintf(stderr, "JNI_GetDefaultJavaVMInitArgs() error\n"); return ret; } if ( (ret = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args)) < 0 ) { fprintf(stderr, "JNI_CreateJavaVM() error\n"); return ret; } // クラスのload。きっとClassLoader.loadClass()と一緒だろう。 clz = (*env)->FindClass(env, "info/imap4rev1/Main"); // methodのsignatureはclassファイルがあるなら javap -sで出力できる // よくわかんないときは javap -sを見よう。(voidのsigが何かって書いてない // JDK docsに附属している仕様って。。。) mid = (*env)->GetMethodID(env, clz, "<init>", "()V"); mobj = (*env)->NewObject(env, clz, mid); mid = (*env)->GetMethodID(env, clz, "doIt", "(Ljava/lang/String;)I"); if ( p == NULL ) { js = getString(env, ""); } else { js = getString(env, p); } ret = (*env)->CallIntMethod(env, mobj, mid, js); // closing (*jvm)->DestroyJavaVM(jvm); return ret; } int main(int argc, char *argv[]) { return doIt(argc > 1 ? argv[1] : NULL); } コンパイルと実行上の注意jvmのi/fを含む soは jreに含まれてます。linux向けのJavaSE6 SDKの場合、 以下3点が同梱されてました。/usr/jdk1.6.0_01/jre/lib/i386/client/libjvm.so 32bit client VM /usr/jdk1.6.0_01/jre/lib/i386/server/libjvm.so 32bit server VM /usr/jdk1.6.0_01/jre/lib/amd64/server/libjvm.so 64bit server VMserver VM/client VMの違いはこの辺にゆずるとして、 64bit linux向けJVM/HotSpot?には、server VMしかないという知らなかった事実を知りましたw Linux/x86_64でJavaアプリケーションを使うときは、使い方次第では 64bitはインスコしない方がいいかもしれません。 さてさて、上記の通りなので、コンパイルと実行の際のldがライブラリを 探すパスに上記の libjvm.soが含まれるディレクトリが入るようにする必要があります。 またコンパイルの際には、ビルドを通す為に -ljvmが必要です。
爽やかな実行結果csh -c "setenv LD_LIBRARY_PATH ${LDPATH} ./doit hoge" Hello hoge World
観想と結論wJNI絡みの日本語資料はけっこうgoogleと出て来ますが、引数のjava objectを 用意して其を元にinvokeするのはあんま無い気がするから、ちょっとはここにも 情報価値があるっぽぃ?wやはりJavaSE6のjvmソースを読みながら書いてみるのがいいらしいです。 上記の実装はそこかしこパクっていて、 例えば、getString()は NewPlatformString?()抜粋wです。
参考リンク
|