Java/JNI

JNI

JNIは、Java Native Interfaceの略であり、JavaとC/C++のネイティブコード間の連携を可能にするインタフェース仕様と技術である。
既存のネイティブで実行するプログラム資産やJavaで対応できないプラットフォーム固有の機能を利用するには、JNIやJava Native Access (JNA) がある。

ネイティブコード実行の手順

  1. ネイティブコードのインタフェースを定義
  2. Javaコードの作成
  3. Javaコードをjavacでコンパイル
  4. javahでヘッダファイルを生成
  5. ネイティブコードをコンパイルし共有ライブラリを作成

サンプルで手順を見ていく。
サンプルで使用するディレクトリ構造は以下の通り。

$ tree .
.
├── Makefile
├── hello.c
└── src
    └── main
        └── java
            └── sample
                └── JNISample.java

4 directories, 3 files

Makefileはビルドと実行の簡単化のために作成する。

ネイティブコードのインタフェースを定義

今回のサンプルで使用するネイティブコードは以下。

#include "JNISample.h"

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コードの作成

Javaプログラムでは、ネイティブコード呼び出しのメソッドはnativeキーワードで指定する。
JNISampleに引数を与えて実行すると、標準出力にメッセージを表示するだけの簡単なサンプルとする。

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でコンパイル

作成したJavaプログラムをコンパイルする。通常通りのjavacコマンドによるコンパイルである。
以下では、classesの下にclassファイルを出力している。

mkdir -p classes
javac -d classes src/main/java/sample/JNISample.java

javahでヘッダファイルを生成

javahコマンドで、ネイティブコードでインクルードするヘッダファイルを生成する。
このヘッダファイルは、jni.hのヘッダファイルをインクルードしているので、hello.c内でJNIインタフェースを使用できる。

javah -o JNISample.h -classpath classes sample.JNISample

デフォルトだと、sample_JNISample.hのようなヘッダファイル名となる。
パッケージ名が長いとファイル名が長くなってしまうこと、ファイル名を固定化したい、の理由で上記では-oオプションで生成されるファイル名を指定する。

生成されたヘッダファイルは以下のようになる。
このファイルは自動生成されるため基本的には修正しない。

/* 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はクラスの参照である。
その後にメソッドの引数定義が続く。

ネイティブコードをコンパイルし共有ライブラリを作成

ネイティブコードをビルドして、共有ライブラリを作成する。
以下はmacOS上でビルドしている。各プラットフォームの仕様を参照されたい。

gcc -O2 -Wall -I$(/usr/libexec/java_home)/include{,/darwin} -shared -o libjnisample.jnilib hello.c

補足 {,darwin}は、Bashのブレース展開

以上でライブラリを作成できたので実際に実行してみる。

$ java -classpath classes sample.JNISample tarou
Hello, tarou

メッセージを表示することができている。

サンプル Makefileの例

Makefile及びサンプルプログラムを添付しておく。

JAVA_HOME   := $(shell /usr/libexec/java_home)
CC           = gcc
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

ビルドと実行方法は以下の通りである。

$ make
$ make ARGS="tarou" run

サンプル JNIサンプルプログラム filejnisample.tar.gz

参考リンク


トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP