4.4. 型の対応

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


4.4.1. JNI引数の例

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)
}

(1)

UTF-8エンコードのバイト配列を指すポインタを戻します。

(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