Edited at

JavaからCの処理を呼ぶ方法(JNI/JNA/SWIG)

More than 5 years have passed since last update.


概要

Linux(CentOS6.5)上でJavaからCの処理を呼び出す方法を調べた際のメモです。簡単な例として以下のようなCの関数をJNI/JNA/SWIGを使ってJavaから使う方法を示します。


hello.c

#include <stdio.h>


void hello (){
printf("Hello World!\n");
}

最初に言っておくと、JNA > swig > JNIの順で簡単だと思います。

多分今後はJNAしか使わないと思いますが、複雑なことをして色々留意点等が出てきたりしたら追記・更新する予定です。


JNI

JNIではCのソースに直接手を加える必要があります。

ということで、hello.cを以下のように書き換えます。


hello.c

#include "HelloJNI.h"

/* Java_HelloJNI_helloのHelloJNIはあとで作るJavaのクラス名、helloはあとで作るnativeメソッド名です。 */
JNIEXPORT void JNICALL Java_HelloJNI_hello (JNIEnv *env, jobject obj) {
printf("Hello World!\n");
}

続いてJavaのソースを書きます。


HelloJNI.java

public class HelloJNI {

static {
// ライブラリのロード。あとで作るlib***.soの***と一致させます。
System.loadLibrary("hello");
}

// nativeメソッドの宣言
public native void hello();

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


javacしてからjavahします。


javacしてjavahする

javac HelloJNI.java 

javah HelloJNI

javacによりHelloJNI.classが、javahによりHelloJNI.hが生成されます。

次にgccコマンドで共有ライブラリファイル(.soファイル)を作成します。


gccで共有ライブラリを作る

gcc -fPIC -shared hello.c -I /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.51.x86_64/include -I  /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.51.x86_64/include/linux/ -o libhello.so


パスは適当に書き換えて下さい。

最後にjavaコマンドを-Djava.library.pathオプションで作成した共有ライブラリファイルが置かれたディレクトリ(今回はカレントディレクトリ)を指定して実行します。


HelloJNIを実行

java -Djava.library.path=. HelloJNI                                                                                                                      

Hello World!

Cを書き換えないといけないので中々大変なのが分かるかと思います。


JNA

JNAではCのソースに手を加える必要がありません。

まず、元のCのソースをコンパイルして共有ライブラリを作成します。


gccで共有ライブラリを作成する。

gcc -fPIC -shared -o libhello.so hello.c


次に公式サイトから最新版のjna.jarを落としてきます。

今回はjna-4.1.0.jarを使いました。

続いて以下のようなJavaのソースを書きます。


HelloJNA.java

import com.sun.jna.Library;

import com.sun.jna.Native;

interface HelloLib extends Library {
// loadLibraryの第一引数はあとで作成するlib***.soの***と一致させる。
HelloLib INSTANCE = (HelloLib) Native.loadLibrary("hello", HelloLib.class);

// Cの関数名と一致させる
void hello();
}

public class HelloJNA {
public static void main(String[] args){
HelloLib hello = HelloLib.INSTANCE;
hello.hello();
}
}


あとはjna.jarにクラスパスを通してコンパイル・実行するだけです。


HelloJNAのコンパイルと実行

javac -cp jna-4.1.0.jar HelloJNA.java

java -cp .:jna-4.1.0.jar HelloJNA

簡単ですね。


SWIG

SWIGはC/C++の処理をJavaに限らず様々な言語から呼び出せる仕組みです。インストールされていなかったらyum等で入れて下さい。

SWIGでは専用の.iファイルを作成する必要があります。今回作るhello.iファイルの中身は以下の通りです。結構シンプルだと思いますが、文法を覚えないといけないので敷居が高いかもしれません。


hello.i

%module hello

%{
#include "stdio.h"
%}
void hello();

続いてswigコマンドを実行します。


swigコマンド実行

swig -java hello.i


実行するとhello.java、helloJNI.java、hello_wrap.cというファイルが出来上がります。hello_wrap.cは長い&可読性が低いので割愛しますが、hello.javaとhelloJNI.javaは以下の様なシンプルなファイルです。


hello.java

public class hello {

public static void hello() {
helloJNI.hello();
}
}


helloJNI.java

class helloJNI {

public final static native void hello();
}

今回は上記を呼び出すために以下の様なMain.javaファイルを作成します。


Main.java

public class Main {

static {
System.loadLibrary("hello");
}
public static void main(String[] args) {
hello.hello();
}
}

javacコマンドでjavaファイルをコンパイルします。


javaファイルのコンパイル

javac *.java


gccコマンドで共有ライブラリを作成します。


Cファイルから共有ライブラリを作成

gcc -fPIC -I /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.51.x86_64/include -I  /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.51.x86_64/include/linux/ -shared -o libhello.so hello.c hello_wrap.c 


最後に以下のように実行します。

java -Djava.library.path=. Main

Hello World!


おまけ

ProcessBuilderとかRuntime#getRuntime()#exec等でもプロセス実行できるので、以下のようなファイルを作っておいて、


main.c

void main (){

hello();
}

コンパイル(gcc -o hello *.c)してJavaから呼ぶ手もあります。

該当箇所をエラーハンドリングとか抜かして書くと↓な感じです。


ProcessBuilderで実行

ProcessBuilder pb = new ProcessBuilder("/path/to/hello");

pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
br.lines().forEach(System.out::println);
System.out.println(process.waitFor());
br.close();


参考にさせて頂いたページ