JNIからネイティブライブラリにデータを渡す場合に、JNIは型をJavaデータ型をC言語で受け取れる型に変換します。
例えばcharを「jchar」、Stringを「jstring」という具合にします。対応表は「表:Java型とネイティブ型の対応」(表4.1「表:Java型とネイティブ型の対応」)を参照ください。
表4.1 表:Java型とネイティブ型の対応
Javaデータ型 | ネイティブ型 | 詳細 |
boolean | jboolean | 8 bit、符号無し |
byte | jbyte | 8 bit、符号付き |
char | jchar | 16 bit、符号無し |
double | jdouble | 64 bit |
float | jfloat | 32 bit |
int | jint | 32 bit、符号付き |
long | jlong | 64 bit、符号付き |
short | jshort | 16 bit、符号付き |
void | void | N/A |
JNIのコードはシンプルなように見えて複雑です。一例を上げて見ましょう。JNIでアクセスするネイティブ関数に引数を指定するとします。
HelloWorldJNI/ ├── jni │ ├── HelloWorld.o │ ├── com_book_jni_HelloWorld.c │ ├── com_book_jni_HelloWorld.h │ └── Makefile ├── pom.xml ├── src │ ├── main │ │ └── java │ │ └── com │ │ └── book │ │ └── jni │ │ └── HelloWorld.java │ └── test │ └── java └── target ├── classes │ ├── com │ │ └── book │ │ └── jni │ │ └── HelloWorld.class │ ├── libhelloworld.dylib │ └── makefile └── test-classes
Javaサイドで引数を指定する場合は、通常のメソッドの引数と同じように取り扱います。
HelloWorld.java.
package com.book.jni; public class HelloWorld { static { System.loadLibrary("helloworld"); } private native void hello(String name); public static void main(String[] args) { HelloWorld hw =new HelloWorld(); String name = "Yamada, Taro"; hw.hello(name); } }
では、javahを使ってヘッダーを生成したとします。すると以下のようなヘッダーが出力されます。
com_book_jni_HelloWorld.h.
#include <jni.h> JNIEXPORT void JNICALL Java_com_book_jni_HelloName_helloWorld (JNIEnv *, jclass, jstring);
Java_com_book_jni_HelloName_helloWorld関数には、jstringという型が指定されています。
private native void hello(String name);
宣言されたhello関数の引数であるnameが、Stringに対応したネイティブ型として表現されています。
最後にこのname引数を標準出力します。
com_book_jni_HelloWorld.c.
#include <jni.h> #include <stdio.h> #include "com_book_jni_HelloWorld.h" #include <OpenCL/cl.h> JNIEXPORT void JNICALL Java_com_book_jni_HelloWorld_hello( JNIEnv *env, jobject thisObj, jstring name) { const char* str = (*env)->GetStringUTFChars(env, name, NULL); //(1) printf("Hello %s\n", str); (*env)->ReleaseStringUTFChars(env, name, str); //(2) }
GetStringUTFCharsはJavaのUTF文字列をC言語で処理可能な、const charを指すポインタに変換してくれます。
Makefile.
# クラスパスを定義 CLASS_PATH = ../target/classes # binディレクトリ内の.classファイルの仮想パスを定義 #vpath %.class $(CLASS_PATH) all : libhelloworld.dylib mv libhelloworld.dylib $(CLASS_PATH) # .dylib(Max OS X)を出力します。 # Windowsの場合は、hello.dll、Linuxの場合は libhello.so libhelloworld.dylib : HelloWorld.o cc -dynamiclib -o $@ $< -framework JavaVM -framework OpenCL # 検証環境ではjdk1.8.0_05を使用しています。インクルードパスはjdkバージョンと合わせます。 HelloWorld.o : com_book_jni_HelloWorld.c cc -c -I/Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/include/ -I /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/include/darwin/ -c $< -o $@ # $* は拡張無しのターゲットファイル名と合わせます。 header_gen : javah -verbose -classpath $(CLASS_PATH) com.book.jni.HelloWorld clean : rm com_book_jni_HelloWorld.h HelloWorld.o $(CLASS_PATH)/libhelloworld.dylib
ビルドについては、Makefileのあるディレクトリ「jni」に移動してから、makeコマンドを発行します。
$ cd jni $ make all cc -c -I/Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/include/ -I /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/include/darwin/ -c com_book_jni_HelloWorld.c -o HelloWorld.o cc -dynamiclib -o libhelloworld.dylib HelloWorld.o -framework JavaVM -framework OpenCL mv libhelloworld.dylib ../target/classes
libhelloworld.dylibは「target/classes」フォルダに移され、シェルもそこに移動します。
$ cd ../target/classes $ java com.book.jni.HelloWorld Hello Yamada, Taro
最後にJavaのクラスファイルを実行すると正常な実行が確認できました。
Copyright 2018-2019, by Masaki Komatsu