GraalVMのnative-imageで共有ライブラリを作る
最近話題のGraalVMのnative-imageで、共有ライブラリを作れるようなので遊んでみた。
やること
おおまかに以下のとおり
- Javaコードを書く
- javac でコンパイル
- native-image で共有ライブラリを生成
- 生成したライブラリを実際に呼び出す
ディレクトリ構成
.
├── c
│ └── main.c
└── java
└── org
└── pkg
└── implnative
└── NativeImpl.java
Javaコードを書く
今回書いたのはこんな感じ。文字列をやりとりするような物にしている。
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.type.CTypeConversion;
import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder;
import org.graalvm.nativeimage.c.type.CCharPointer;
public final class NativeImpl {
@CEntryPoint(name = "Java_org_pkg_apinative_Native_add")
static int add(IsolateThread thread, int a, int b) {
return a + b;
}
@CEntryPoint(name = "Java_org_pkg_apinative_Native_rptstr")
static CCharPointer repeatString(IsolateThread thread, CCharPointer cStr, int i) {
String jStr = CTypeConversion.toJavaString(cStr);
return CTypeConversion.toCString(IntStream.range(0, i).mapToObj(x -> jStr).collect(Collectors.joining())).get();
}
@CEntryPoint(name = "Java_org_pkg_apinative_Native_hello")
static CCharPointer hello(IsolateThread thread) {
return CTypeConversion.toCString("hello from native lib").get();
}
}
CEntryPoint
アノテーションを付与することで共有ライブラリに出力することができ、実際に呼び出すときの名前を指定することができる。
CEntryPoint
が付与されたメソッドは以下の制約がある。これを満たさないと、共有ライブラリ化のタイミングでエラーが発生する。
static
でなければならない- 引数、戻り値はプリミティブ型もしくはポインタ型で無ければならない
- 引数に
IsolateThread
またはIsolate
型の物を含まなければならない
なお、これらの見慣れないクラス群は、 ${GRAAL_HOME}/jre/lib/boot/graal-sdk.jar
に含まれているが、GraalVM同梱のjavacでコンパイルする場合はクラスパスに指定する必要はない。
IDEで赤線を引かれないためには色々設定が必要かもしれないが今回は割愛。
javac でコンパイル
.java
と同じディレクトリに .class
が生成される。
${GRAAL_HOME}/bin/javac java/org/pkg/implnative/NativeImpl.java
native-image で共有ライブラリを生成
いよいよ共有ライブラリを生成する。 --shared
を指定する場合、 -H:Name
を要求されるので適当につけておく。
$GRAAL_HOME/bin/native-image --shared -H:Name=libnativeimpl -cp java
うまくいけば、 共有ライブラリと、それを利用するためのヘッダファイル群が生成される。
内容を見ると、CEntryPoint
アノテーションで指定した名前で関数が生成されている。
int Java_org_pkg_apinative_Native_add(graal_isolatethread_t*, int, int);
char* Java_org_pkg_apinative_Native_rptstr(graal_isolatethread_t*, char*, int);
char* Java_org_pkg_apinative_Native_hello(graal_isolatethread_t*);
生成したライブラリを実際に呼び出す
ライブラリなので、単体では動かせない。ので、実際にCから呼び出してみる。
C のコード
#include <stdio.h>
#include <stdlib.h>
#include <libnativeimpl.h>
int main(void) {
graal_isolatethread_t *thread = NULL;
if (graal_create_isolate(NULL, NULL, &thread) != 0) {
fprintf(stderr, "error on isolate creation or attach\n");
return 1;
}
int result = Java_org_pkg_apinative_Native_add(thread, 1, 2);
printf("%d\n", result);
char* hello = Java_org_pkg_apinative_Native_hello(thread);
printf("%s\n", hello);
char* repeat = Java_org_pkg_apinative_Native_rptstr(thread, "ABC", 3);
printf("%s\n", repeat);
graal_tear_down_isolate(thread);
}
コンパイル & 実行
リンカオプションはCのファイル名の後に指定する必要がある(ハマった)
gcc -Wall -I ./ c/main.c -L ./ -l nativeimpl -Wl,-rpath='$ORIGIN/'
コンパイルに成功すれば、実行ファイルが生成されるはず。
./a.out
3
hello from native lib
ABCABCABC
めでたい 🎉