Help us understand the problem. What is going on with this article?

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

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

kiida
San Joseの教育系スタートアップ(YC W18)でCTO兼フルスタックソフトウェアエンジニアしてます。その前は同じくSan Joseの採用系スタートアップ、その前は日本のITコンサル(SIer?)的なところにいました。気軽に絡んでください。
https://daikikohara.github.io/about/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away