LoginSignup
2
4

More than 3 years have passed since last update.

jnr-ffi使った(使いやすくするなどした)

Last updated at Posted at 2019-01-13

用語説明

JNR (Java Native Runtime)

JavaからNativeなコードを呼び出す仕組み
Project Panamaのメインコンテンツ
JEP 191: Foreign Function Interfaceが提案されている

FFI (Foreign Function Interface)

InterfaceまではJavaで定義して(しない)、実装をCやC++でできる優れもの
LibraryLoaderというやつを使ってダイナミックリンクライブラリ(.dllや.so)からオブジェクトを生成して呼び出す

FFIを使ってみる

プロジェクト作成

JDKのバージョンを12に指定(意味があるかは不明)

ライブラリを追加

build.gradleのdependancesにライブラリを追加します。

version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

//追加部分
dependencies {
    //FFIの本体
    compile group: 'com.github.jnr', name: 'jnr-ffi', version: '2.1.9'
    //FFIが依存するライブラリ
    compile group: 'org.ow2.asm', name: 'asm', version: '7.0'
}

関数定義のためにInterfaceを作る

Cで作ったライブラリを呼び出す為、仕様をInterfaceで定義します。
Interfaceでのメソッド定義は、ヘッダファイル(.h)での関数宣言と揃えます。

今回は、最大公約数を求める、ユークリッドの互除法を実装しました

Interfaceを作成する

euclid.hと同じ内容を、Interfaceで記述します。

fact.java
public interface test {
    int euclid(int m, int n);
}

Mainクラスを作成する

呼び出し側のJavaファイルを作成します。

Test.java
import jnr.ffi.LibraryLoader;

public class Test {
    public static void main(String[] args) {
        System.out.println(
            /*  fact型のオブジェクトをロードする
                createでは、指定するInterfaceのclassを渡す
                loadの引数では、文字列でdllファイルのファイル名(拡張子を除く)を渡す */
            LibraryLoader.create(test.class).load("test")
                //euclidを実行する
                .euclid(2,4)
        );
    }
}

Cで処理を実装する

euclid.h
int euclid(int i, int j);
euclid.c
int euclid(int m, int n){
    int tmp, r;
    if(n > m){
        tmp = m;
        m = n;
        n = tmp;
    }
    r = m % n;
    if(r == 0) return n;
    return euclid(n, r);
}

はい、普通にCでヘッダファイルとプログラムを実装しました。

.dllファイルを生成する

Foreign Function Interfaceでは、ダイナミックリンクライブラリを利用するため、.dllファイルを生成します。
dllを作成するときは、OSのbit数に合わせたものを作成しましょう。
ちなみに、OSやbit数が違う・そもそもdllがないなどの状態で実行すると、
java.lang.UnsatisfiedLinkError: unknown
というエラーがでます。

筆者は、MinGWのgccを使いました。

gcc -shared -o test.dll test.cpp

システム・プロパティの指定

作成した.dllファイルをプロジェクトに組み込むために、システムプロパティのjava.library.pathにパスを渡す必要があります。
Javaでは、javaコマンドの実行時に、以下のようにシステムプロパティを設定できます。

java -D<プロパティ名>=<プロパティ>

今回の場合は、このように実行することで.dllファイルを組み込むことが出来ます。

java -Djava.library.path=C:\\test.dll Test

Gradleでシステム・プロパティの指定

Gradleで実行するときは、わざわざjavaコマンドを打つことはしないですよね?
そこで、build.gradleに少し追加することで、実行ボタンで実行するときにシステムプロパティを渡せるようにします。

//タスク名は任意?
task launch(type: JavaExec) {
    //systemPropertyを指定したいメインクラス名を指定
    main = "Test"
    //ここはおまじない
    classpath sourceSets.main.runtimeClasspath
    systemProperty "java.library.path", "<.dllファイルのパス>"
}

Interface生成の自動化

jextract

.hファイルから(Interfaceが入った)jarを自動生成するツール

対応OS

jextractは、現在Project PanamaのEarly Access版にのみ同梱されています。
困ったことに、現在(2019/1/14)はLinux版とMac版しか用意されていないようです。
仕方がないので、筆者はBash on Ubuntu on Windowsを導入しました。

Panama Early Accessのインストール

普通にwgetでバイナリを貰ってきました。
make installなどは不要なようです。
tarコマンドで解凍すればすぐに利用する事ができます
ついでにパスも通しておきましょう

wget https://download.java.net/java/early_access/panama/archive/0/binaries/jdk-12-foreign+0_linux-x64_bin.tar.gz

jextractの実行

.hファイルを指定するだけです。

jextract test.h

生成されたjarファイル(ここではtest.h.jar)をプロジェクトに追加します。
(編集 -> プロジェクト構造 とかでライブラリに指定します。)

これで、一々同じ記述をInterfaceとヘッダに記述する必要がなくなりました。

本題

で、このjarとdllを置くときに一々ファイル名をプロパティに入れたりするのが嫌だったのと、jar内にdll入れた場合のシステムプロパティのしてお方法が分からなかったので、代わりにやってくれる奴を作りました。

プログラムはgithubに置いておくので、使いたい人は拾って使ってください。
Javakky/UseC4ffi4Windows

使い方

dllをリソースフォルダに置きます。
loadDllに、.dllファイルのパス(/resourceより下)とInterfaceのクラスを渡すだけで、オブジェクトが返ってきます。

LoadDllTest.java
public class LoadDllTest {
    public static void main(String[] args) {
        //これだけでオブジェクトが取れる
        IClassA func = LoadDll.loadDll("<.dll名>", <インターフェース>.class));
    }
}

gradleで使う

bintryで公開してgradleプロジェクトからも利用できるようにしました。

build.gradle
repositories {
    maven {
        url 'https://dl.bintray.com/javakky/maven'
    }
}

dependencies{
    compile group: 'com.github.javakky', name: 'jnr-load-dill', version: '1.0.1'
}

参考にした記事一覧

jnr-ffi関係記事リンク集

2
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4