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-classesJavaサイドで引数を指定する場合は、通常のメソッドの引数と同じように取り扱います。
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