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

# 用語説明

## JNR (Java Native Runtime)
JavaからNativeなコードを呼び出す仕組み
[Project Panama](https://openjdk.java.net/projects/panama/)のメインコンテンツ
[JEP 191: Foreign Function Interface](https://openjdk.java.net/jeps/191)が提案されている

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

# FFIを使ってみる

## プロジェクト作成
JDKのバージョンを12に指定(意味があるかは不明)

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

```groovy
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)での関数宣言と揃えます。

今回は、最大公約数を求める、[ユークリッドの互除法](http://www.u-gakugei.ac.jp/~miyadera/LECTURE/ElecBook2/ptech11.htm)を実装しました

## Interfaceを作成する

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

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

## Mainクラスを作成する
呼び出し側のJavaファイルを作成します。

```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で処理を実装する

```c:euclid.h
int euclid(int i, int j);
```

```c: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](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/rubenvb/)のgccを使いました。

```shell
gcc -shared -o test.dll test.cpp
```

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

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

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

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

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

```groovy
//タスク名は任意？
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版](http://jdk.java.net/panama/)にのみ同梱されています。
困ったことに、現在(2019/1/14)はLinux版とMac版しか用意されていないようです。
仕方がないので、筆者は[Bash on Ubuntu on Windowsを導入](https://qiita.com/pepo/items/52a19ce5994ebaff9add)しました。

## Panama Early Accessのインストール
普通にwgetでバイナリを貰ってきました。
make installなどは**不要**なようです。
tarコマンドで解凍すればすぐに利用する事ができます
ついでに[パスも通しておきましょう](https://qiita.com/takanemu/items/6027291be8a2a6af9adc)。

```shell
wget https://download.java.net/java/early_access/panama/archive/0/binaries/jdk-12-foreign+0_linux-x64_bin.tar.gz
```
## jextractの実行
.hファイルを指定するだけです。

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

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

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

プログラムはgithubに置いておくので、使いたい人は拾って使ってください。
[Javakky/UseC4ffi4Windows](https://github.com/Javakky/UseC4ffi4Windows)

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

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

## gradleで使う

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

```groovy: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関係記事リンク集](https://qiita.com/Kakky/items/bce3d6c9c47899b000a3)