概要
Linux(CentOS6.5)上でJavaからCの処理を呼び出す方法を調べた際のメモです。簡単な例として以下のようなCの関数をJNI/JNA/SWIGを使ってJavaから使う方法を示します。
#include <stdio.h>
void hello (){
printf("Hello World!\n");
}
最初に言っておくと、JNA > swig > JNIの順で簡単だと思います。
多分今後はJNAしか使わないと思いますが、複雑なことをして色々留意点等が出てきたりしたら追記・更新する予定です。
JNI
JNIでは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のソースを書きます。
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 HelloJNI.java
javah HelloJNI
javacによりHelloJNI.classが、javahによりHelloJNI.hが生成されます。
次にgccコマンドで共有ライブラリファイル(.soファイル)を作成します。
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オプションで作成した共有ライブラリファイルが置かれたディレクトリ(今回はカレントディレクトリ)を指定して実行します。
java -Djava.library.path=. HelloJNI
Hello World!
Cを書き換えないといけないので中々大変なのが分かるかと思います。
JNA
JNAではCのソースに手を加える必要がありません。
まず、元のCのソースをコンパイルして共有ライブラリを作成します。
gcc -fPIC -shared -o libhello.so hello.c
次に公式サイトから最新版のjna.jarを落としてきます。
今回はjna-4.1.0.jarを使いました。
続いて以下のような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にクラスパスを通してコンパイル・実行するだけです。
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ファイルの中身は以下の通りです。結構シンプルだと思いますが、文法を覚えないといけないので敷居が高いかもしれません。
%module hello
%{
#include "stdio.h"
%}
void hello();
続いてswigコマンドを実行します。
swig -java hello.i
実行するとhello.java、helloJNI.java、hello_wrap.cというファイルが出来上がります。hello_wrap.cは長い&可読性が低いので割愛しますが、hello.javaとhelloJNI.javaは以下の様なシンプルなファイルです。
public class hello {
public static void hello() {
helloJNI.hello();
}
}
class helloJNI {
public final static native void hello();
}
今回は上記を呼び出すために以下の様なMain.javaファイルを作成します。
public class Main {
static {
System.loadLibrary("hello");
}
public static void main(String[] args) {
hello.hello();
}
}
javacコマンドでjavaファイルをコンパイルします。
javac *.java
gccコマンドで共有ライブラリを作成します。
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
等でもプロセス実行できるので、以下のようなファイルを作っておいて、
void main (){
hello();
}
コンパイル(gcc -o hello *.c
)してJavaから呼ぶ手もあります。
該当箇所をエラーハンドリングとか抜かして書くと↓な感じです。
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();
参考にさせて頂いたページ
- JNI/JNA
- http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
- http://www.atmarkit.co.jp/fjava/special/jna/jna_1.html
- http://blue-red.ddo.jp/~ao/wiki/wiki.cgi?page=JNI%A1%A2JNA%A4%CE%BB%C8%A4%A4%CA%FD
- SWIG
- http://www.swig.org/
- http://d.hatena.ne.jp/gymno/20110728/1311869632
- gcc option
- http://gcc.gnu.org/onlinedocs/gcc/Option-Index.html
- http://linuxjm.sourceforge.jp/html/GNU_gcc/man1/gcc.1.html