- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2020-04-19T05:00:12+00:00","default:haikikyou","haikikyou")
#author("2020-04-19T10:40:54+00:00","default:haikikyou","haikikyou")
[[Java/JNI]]
#contents
* JNI [#s2524a7e]
JNIは、'''Java Native Interface'''の略であり、JavaからC/C++のネイティブコードを実行するための仕様と技術である。~
JNIは、'''Java Native Interface'''の略であり、JavaとC/C++のネイティブコード間の連携を可能にするインタフェース仕様と技術である。~
既存のネイティブで実行するプログラム資産やJavaで対応できないプラットフォーム固有の機能を利用するには、JNIやJava Native Access (JNA) がある。
* ネイティブコード実行の手順 [#j0729c3e]
+ ネイティブコードの特定
+ ネイティブコードのインタフェースを定義
+ Javaコードの作成
+ Javaコードをjavacでコンパイル
+ javahでヘッダファイルを生成
+ ネイティブコードをコンパイルし共有ライブラリを作成
サンプルで手順を見ていく。~
サンプルで使用するディレクトリ構造は以下の通り。
#geshi{{{
~/workspace/java/jni/jnisample $ tree .
$ tree .
.
├── Makefile
├── hello.c
└── src
└── main
└── java
└── sample
└── JNISample.java
4 directories, 3 files
}}}
- パッケージsampleにJavaプログラムを配置する。
- hello.cにネイティブコードを書き、JNISample.javaからhello.cの関数を呼び出す。
Makefileはビルドと実行の簡単化のために作成する。
** ネイティブコードのインタフェースを定義 [#g6fb7430]
今回のサンプルで使用するネイティブコードは以下。
#geshi(c){{{
#include "JNISample.h"
JNIEXPORT void JNICALL Java_sample_JNISample_hello (JNIEnv *env, jobject obj, jstring jmsg) {
JNIEXPORT void JNICALL
Java_sample_JNISample_hello (JNIEnv *env, jobject obj, jstring jmsg)
{
jboolean isCopy;
const char *msg = (*env)->GetStringUTFChars(env, jmsg, &isCopy);
if (msg == NULL)
return;
printf("Hello, %s\n", msg);
(*env)->ReleaseStringUTFChars(env, jmsg, msg);
}
}}}
** Javaコードの作成 [#d5bef258]
Javaプログラムでは、ネイティブコード呼び出しのメソッドはnativeキーワードで指定する。~
Javaプログラムでは、ネイティブコード呼び出しのメソッドは&code(){native};キーワードで指定する。~
JNISampleに引数を与えて実行すると、標準出力にメッセージを表示するだけの簡単なサンプルとする。
#geshi(java){{{
package sample;
class JNISample {
static {
System.loadLibrary("jnisample");
}
// C側のプログラムを呼び出すインターフェース
public native void hello(String message);
public static void main(String[] args) {
JNISample hello = new JNISample();
hello.hello(args[1]);
}
}
}}}
** Javaコードをjavacでコンパイル [#d7f6736b]
作成したJavaプログラムをコンパイルする。通常通りのjavacコマンドによるコンパイルである。~
作成したJavaプログラムをコンパイルする。通常通りの&code(){javac};コマンドによるコンパイルである。~
以下では、classesの下にclassファイルを出力している。
#geshi{{{
mkdir -p classes
javac -d classes src/main/java/sample/JNISample.java
}}}
** javahでヘッダファイルを生成 [#x96bdfbc]
javahコマンドで、ネイティブコードでインクルードするヘッダファイルを生成する。~
このヘッダファイルは、jni.hのヘッダファイルをインクルードしているので、hello.c内でJNIインタフェースを使用できる。
&code(){javah};コマンドで、ネイティブコードでインクルードするヘッダファイルを生成する。~
このヘッダファイルは、&code(){jni.h};のヘッダファイルをインクルードしているので、hello.c内でJNIインタフェースを使用できる。
#geshi(java){{{
javah -o JNISample.h -classpath classes sample.JNISample
}}}
デフォルトだと、&code(){sample_JNISample.h};のようなヘッダファイル名となる。~
パッケージ名が長いとファイル名が長くなってしまうこと、ファイル名を固定化したい、の理由で上記では&code(){-o};オプションで生成されるファイル名を指定する。
生成されたヘッダファイルは以下のようになる。~
このファイルは自動生成されるため基本的には修正しない。
#geshi(c){{{
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class sample_JNISample */
#ifndef _Included_sample_JNISample
#define _Included_sample_JNISample
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: sample_JNISample
* Method: hello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_sample_JNISample_hello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
}}}
JNIで自動生成されるメソッドの引数には、JNIEnvとjobject(staticの場合は、jclass)が渡される。~
jobjectはオブジェクト、jclassはクラスの参照である。~
その後にメソッドの引数定義が続く。
** ネイティブコードをコンパイルし共有ライブラリを作成 [#f0e9831e]
ネイティブコードをビルドして、共有ライブラリを作成する。~
以下はmacOS上でビルドしている。各プラットフォームの仕様を参照されたい。
#geshi(bash){{{
gcc -O2 -Wall -I$(/usr/libexec/java_home)/include -I$(/usr/libexec/java_home)/include/darwin -shared -o libjnisample.jnilib hello.c
gcc -O2 -Wall -I$(/usr/libexec/java_home)/include{,/darwin} -shared -o libjnisample.jnilib hello.c
}}}
&label(info){補足}; &code(){{,darwin}};は、Bashのブレース展開
以上でライブラリを作成できたので実際に実行してみる。
#geshi(bash){{{
$ java -classpath classes sample.JNISample tarou
Hello, tarou
}}}
メッセージを表示することができている。
&label(sample){サンプル}; ''Makefile''
&label(sample){サンプル}; ''Makefileの例''
Makefile及びサンプルプログラムを添付しておく。
#geshi(make){{{
JJAVA_HOME := $(shell /usr/libexec/java_home)
JAVA_HOME := $(shell /usr/libexec/java_home)
CC = gcc
CFLAGS = -O2 -Wall -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/darwin
CFLAGS = -O2 -Wall $(addprefix -I$(JAVA_HOME)/,include include/darwin)
SOURCES = hello.c
OBJS = $(SOURCES:%.c=%.o)
PROGRAM = libjnisample.jnilib
CLASSES = src/main/java/sample/JNISample.java
.SUFFIXES: .java .class
.PHONY: all
all: $(PROGRAM)
$(PROGRAM): JNISample.h $(OBJS)
$(CC) $(CFLAGS) -shared $(OBJS) -o $@
JNISample.h: classes
javah -o JNISample.h -classpath classes sample.JNISample
.PHONY: classes
classes: $(CLASSES:.java=.class)
.java.class:
mkdir -p classes
javac -d classes $<
.PHONY: run
run:
java -classpath classes -Djava.library.path=. sample.JNISample $(ARGS)
.PHONY: clean
clean:
$(RM) -rf *.o *.jnilib classes JNISample.h
}}}
ビルドと実行方法は以下の通りである。
#geshi{{{
$ make
$ make ARGS="tarou" run
}}}
&label(sample){サンプル}; ''JNIサンプルプログラム''
&ref(jnisample.tar.gz);
* 参考リンク [#g542d2d3]
- [[Java Native Interface仕様の目次>https://docs.oracle.com/javase/jp/8/docs/technotes/guides/jni/spec/jniTOC.html]] - &size(11){&color(gray){on https://docs.oracle.com/javase/jp/8/docs/technotes/guides/jni/spec/jniTOC.html};};
- [[NI:Java Native Interfaceプログラミング―C/C++コードを用いたJavaアプリケーション開発 (Java books)>https://www.amazon.co.jp/dp/4894710803]] -- &size(11){&color(gray){on https://www.amazon.co.jp/dp/4894710803};};
- [[JNI Example (Mac OS)>https://gist.github.com/DmitrySoshnikov/8b1599a5197b5469c8cc07025f600fdb]] - &size(11){&color(gray){on https://gist.github.com/DmitrySoshnikov/8b1599a5197b5469c8cc07025f600fdb};};
- [[eginning Java 8 APIs, Extensions and Libraries: Swing, JavaFX, JavaScript, JDBC and Network Programming APIs (Expert's Voice in Java)>https://www.amazon.co.jp/dp/1430266619/]] - &size(11){&color(gray){on https://www.amazon.co.jp/dp/1430266619/};};
- AdoptOpenJDK/openjdk-jdk8u
-- [[hotspot/src/share/vm/prims/jni.h>https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/hotspot/src/share/vm/prims/jni.h]] - &size(11){&color(gray){on https://github.com/AdoptOpenJDK/openjdk-jdk8u/blob/master/hotspot/src/share/vm/prims/jni.h};};