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

# 概要

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

```c:hello.c
#include <stdio.h>

void hello (){
  printf("Hello World!\n");
}
```
最初に言っておくと、JNA > swig > JNIの順で簡単だと思います。
多分今後はJNAしか使わないと思いますが、複雑なことをして色々留意点等が出てきたりしたら追記・更新する予定です。

# JNI

JNIではCのソースに直接手を加える必要があります。
ということで、hello.cを以下のように書き換えます。

```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のソースを書きます。

```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します。

```bash:javacしてjavahする
javac HelloJNI.java 
javah HelloJNI
```

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

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

```bash: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オプションで作成した共有ライブラリファイルが置かれたディレクトリ(今回はカレントディレクトリ)を指定して実行します。

```java:HelloJNIを実行
java -Djava.library.path=. HelloJNI                                                                                                                      
Hello World!
```

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

# JNA

JNAではCのソースに手を加える必要がありません。
まず、元のCのソースをコンパイルして共有ライブラリを作成します。

```bash:gccで共有ライブラリを作成する。
gcc -fPIC -shared -o libhello.so hello.c
```

次に[公式サイト](https://github.com/twall/jna)から最新版のjna.jarを落としてきます。
今回はjna-4.1.0.jarを使いました。

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

```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にクラスパスを通してコンパイル・実行するだけです。

```bash:HelloJNAのコンパイルと実行
javac -cp jna-4.1.0.jar HelloJNA.java
java -cp .:jna-4.1.0.jar HelloJNA
```
簡単ですね。

# SWIG

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

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

```:hello.i
%module hello
%{
#include "stdio.h"
%}
void hello();
```

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

```bash:swigコマンド実行
swig -java hello.i
```

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

```java:hello.java
public class hello {
  public static void hello() {
    helloJNI.hello();
  }
}
```

```java:helloJNI.java
class helloJNI {
  public final static native void hello();
}
```

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

```java:Main.java
public class Main {
  static {
    System.loadLibrary("hello");
  }
  public static void main(String[] args) {
    hello.hello();
  }
}
```

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

```bash:javaファイルのコンパイル
javac *.java
```

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

```bash: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`等でもプロセス実行できるので、以下のようなファイルを作っておいて、

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

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

```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();
```


# 参考にさせて頂いたページ
- 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