JNIでHelloWorld!

JNIはJavaプログラムからC/C++で記述されたライブラリのAPIを呼び出すためのインタフェースです。ずいぶん昔からある仕様ですが、あまりなじみが無いので触ってみた。

イメージ的にはELF形式のjavaプログラムから共有ライブラリに対してdlopenで動的にリンクして呼び出すって感じですな。MySQLのplugin(UDF, pluggable storage engine)と技術的には同類。

ということで以下は、Linux x86 (CentOS4.5) + JavaSE6 でJNI HelloWorldした記録です。

参考:http://www.alles.or.jp/~torutk/oojava/maneuver/2001/jni/jni.html

ダウンロード&インストール

ここからJavaSE6のJDKを入手してインストール。RPMが楽でいい。お奨め。

既存環境にgcj(java-1.4.2-gcj-compat.noarch)とかが入っているとインストールがうまく行かないので先に抜いておく。

以下のコマンドでインストール完了確認

java -version

どうやらRPMで入れると、

/usr/bin/java等
↓
/usr/java/default/***
↓
/usr/java/latest/***
↓
/usr/java/jdk1.6.0_11

みたいにつながるようにセットアップしてくれる(RPM版)。

ただし、javahコマンドは/usr/binにsymlinkが作られないので

cd /usr/bin && sudo ln -s /usr/java/default/javah

する必要あり。

Javaプログラム作成

こんな感じのクラスを作る。

package tritonn;

public class HelloJNI {
    static {
        System.loadLibrary("HelloJNIImpl");
    }

    public native void printHello();

    public static void main(String[] args) {
        new HelloJNI().printHello();
    }
}

ポイントはstaticイニシャライザでSystem.loadLibraryを呼ぶこと。JNI経由で呼ぶ予定のメソッドをnative修飾子付きの宣言だけしておくこと。

Javaプログラムのコンパイル

普通にjavacすればよろし。

javac tritonn/HelloJNI.java

Cヘッダ作成

javahコマンドでCプログラム用のヘッダを自動生成する。完全修飾クラス名で指定。

javah tritonn.HelloJNI

作業ディレクトリにこんなヘッダファイルができる。tritonn_HelloJNI.hとか。(パッケージ名_クラス名.hかな)

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class tritonn_HelloJNI */

#ifndef _Included_tritonn_HelloJNI
#define _Included_tritonn_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     tritonn_HelloJNI
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_tritonn_HelloJNI_printHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

これを実装したCプログラムを作る。

Cプログラム作成

いくつかなじみの無いマクロがでてきてますが、気にせずヘッダファイルにある関数シグネチャ通りに実装。

#include <jni.h>
#include "tritonn_HelloJNI.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_tritonn_HelloJNI_printHello
(JNIEnv * a, jobject b) {
  printf("Hello JNI World!!\n");
  return;
}

Cプログラムのコンパイル

JDKに含まれているjni.hとかは(RPM版でインストールしても)/usr/includeとかには入らないので、-Iで明示する必要有り。また共有ライブラリにするので -fPIC を忘れずに。

gcc -fPIC -I/usr/java/default/include -I/usr/java/default/include/linux -c HelloJNIImpl.c

共有ライブラリの作成

ここで指定するライブラリ名がJavaプログラムでSystem.loadLibraryする対象となります。

gcc -shared -o libHelloJNIImpl.so HelloJNIImpl.o

実行

とりあえずLD_LIBRARY_PATH指定して実行。

tritonn@dev32:~$ LD_LIBRARY_PATH=`pwd` java tritonn.HelloJNI
Hello JNI World!!

おぉぉ!うまく行きましたね!

/usr/libとかに*.soを放り込んでldconfigを実行すればLD_LIBRARY_PATHは不要になると思います。