差分表示
- 最後の更新で追加された行はこのように表示します。
- 最後の更新で削除された行は
このように表示します。
*Java native Interface
だいぶ前に使ったが、すっかりわすれていて久し振りに使いたくなったので
ついでに忘れてもいいようにここにもメモしとこうと思いました φ(。。
JDK6のドキュメントに含まれているJNIのドキュメントの日付は2003年となっているので、
新しいことは無いと思われるが、一応一通りRHEL-as4u4/jdk6u1でやってみる。
*Java2native
まずは Javaクラスメソッドの実体をnativeで実装する場合からスタート。
このパターンは 順当にクラスメソッドの実体をnativeで実装することになる。
従って作業は以下の通り。
-1. Javaコード部分はinterfaceクラスみたいな宣言する
-2. 実装があるライブラリを動的にロードする
-3. メソッドの実体をnativeで作る
-4. 作った実装をshared libraryにする
*さんぷる
基本の printf("Hello JNI World\n")だろうwと言うことで、こんな感じ。
-doIt()メソッドで stdoutに '''Hello %s World\n''' を出力する。%s部分は引数のStringにする
-doIt()の引数はString/戻り値はintで、successなら1、失敗は -1。
-引数文字列の文字列長が0、またはNULL Pointerの場合、失敗でなにもしない
**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に従う。
-Java_[FQCN]_[method名](__引数名)
-(*1) 末尾のかっこは、このmethodがoverloadされている場合、他との区別のために必要
こういうのは実際のコードを見るのが一番分り易いと思う。何年か昔だったら、J2MEの
Linux向けriが配付されていたので、それを見れと言うところだが、今の時分では opekjdkなる
時代になったので、jdk6/jdk7(=OpenJDK)のソースを直接見て、Sunが提供しているJDKの
直接のソースを[[この辺から>http://community.java.net/openjdk/]]見ることが可能である。
いい時代になったねぇ。
ま、よた話はおいといて、今回の場合のmethodに対応する関数名は以下の通りになる。
-Java_info_imap4rev1_Main_doIt
で、引数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
仕方がないので作り方の手順もここに含めておく。
-1. Javaクラスファイルをjavacでつくっておく
-2. 1.で作ったクラスファイルをjavahに食わせるとheaderファイルが出来る
--'''javah -classpath . info.imap4rev1.Main'''
--このコマンドの結果、info_imap4rev1_Main.hが下記の通り、作成されるだろう。
--classpathの通り方は普通にjvmを使っている時と一緒なので .を通す必要もないけど、書いといた。
---(
/* 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
---)
-3. 2.で出来たheaderを元に実体を書く。
-4. $JAVA_HOME/includeと $JAVA_HOME/include/[arch]を-Iのパスに通してコンパイルする。
--'''gcc -c -o hoge.o -fPIC -I/usr/java/include -I/usr/java/include/linux hoge.c''' (*1)
-5. shard libraryにする。(無論、4とセットでもいい)
--'''gcc -shared -o libtest.so hoge.o''' (*1)
-(*1) x86_64のlinuxで、javaをLinux64 bit対応のバイナリも併せて展開していない(=32bit VM)場合は、dynamic loadするライブラリも32bitにしないとUnsatisfiedLinkErrorになる(ファイルはあるのに ''cannot open shared object file: No such file or directory''という怪しい状態でこのランタイムエラーが出る)。この場合、gccでobject/libraryを作成する際に''-m32''とarchを指定しておく。
**実行結果
---(
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実装を作るだけで、中でやることは下記の通り。
-1. JVMの起動引数を用意して、CreateJavaVMする
-2. Main classをloadして、newする
-3. コマンドライン引数をdoItの引数にするためのコンテナStringをnewする
-4. doItをinvokeする
**さんぷる
動作仕様はこんな感じでいいだろう。
-Mainのクラスをinstance化する
-Main.doIt()の引数にコマンドライン引数があれば、コマンドライン引数を渡す。コマンドライン引数が無い場合はNULLを渡す
%%どうみてもテンプラコードでokなので、読んでるチュートリアルのコードをぱくるとこんな感じ。%%~
爽やかにjdk6の実装を参考にしつつ、書いてみました。java2nativeの時に比べて
説明の章割りが少ないのは、作るものが1個だからです。
例によってコードのコメントとして解説をうめました。
途中でめんどうになったので、見れば判る通り例外処理を大量に端折ってます。
ちゃんと書くなら 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 VM
server VM/client VMの違いは[[この辺>J2SE5.0_hotspot_memo]]にゆずるとして、
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
***観想と結論w
JNI絡みの日本語資料はけっこうgoogleと出て来ますが、引数のjava objectを
用意して其を元にinvokeするのはあんま無い気がするから、ちょっとはここにも
情報価値があるっぽぃ?w
やはりJavaSE6のjvmソースを読みながら書いてみるのがいいらしいです。
上記の実装はそこかしこパクっていて、
例えば、getString()は NewPlatformString()抜粋wです。
*参考リンク
-JNIドキュメント
--http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/jni/spec/jniTOC.html
--ここをベースに書いてみた
-OpenJDK
--http://openjdk.java.net/
--glassfish(=J2EE5)の時も、dev.java.netは偉大wだったけど、JavaDeveloperには一段と有用なサイトになったようです。IBMのdWの方が有用な情報がjava.sun.comドメインより多いって思った時期もありましたが、見ることが出来るsrcを提供してるから、性質は違うけど有用度はこちらもとても高いって言うか、最期の依どころですな
Last-modified: 2007-06-12 17:06:52