共有ライブラリのビルド
ネイティブ共有ライブラリをビルドするには、次のように、コマンドライン引数--shared
をnative-image
ツールに渡します
native-image <class name> --shared
JARファイルからネイティブ共有ライブラリをビルドするには、次の構文を使用します:
native-image -jar <jarfile> --shared
生成されたネイティブ共有ライブラリには、指定されたJavaクラスのmain()
メソッドがエントリポイント・メソッドとして含まれています。
ライブラリにmain()
メソッドが含まれていない場合は、次のように-H:Name=
コマンドライン・オプションを使用して、ライブラリ名を指定します:
native-image --shared -H:Name=<libraryname> <class name>
native-image --shared -jar <jarfile> -H:Name=<libraryname>
GraalVMを使用すると、Cを使用してネイティブ共有ライブラリを簡単に呼び出すことができます。ネイティブ共有ライブラリに埋め込まれたメソッド(関数)をコールするには、主にネイティブ・イメージC APIとJNI呼出しAPIの2つのメカニズムがあります。
このガイドでは、ネイティブ・イメージC APIを使用する方法について説明します。これには次のステップが含まれます。
- 少なくとも1つのエントリポイント・メソッドを含むJavaクラス・ライブラリを作成してコンパイルします。
native-image
ツールを使用して、Javaクラス・ライブラリから共有ライブラリを作成します。- 共有ライブラリ内でエントリポイント・メソッドを呼び出すCアプリケーションを作成してコンパイルします。
ヒントとコツ
共有ライブラリには、少なくとも1つのエントリポイント・メソッドが必要です。デフォルトでは、public static void main()
メソッドからのmain()
というメソッドのみがエントリポイントとして識別され、Cアプリケーションからコールできます。
他のJavaメソッドをエクスポートするには:
- メソッドを静的として宣言します。
- メソッドに
@CEntryPoint
(org.graalvm.nativeimage.c.function.CEntryPoint
)で注釈を付けます。 IsolateThread
またはIsolate
タイプのメソッドのパラメータの1つを作成します。たとえば、次のメソッドの最初のパラメータ(org.graalvm.nativeimage.IsolateThread
)などです。このパラメータは、コールに対して現在のスレッドの実行コンテキストを指定します。- パラメータおよび戻り型を非オブジェクト型に制限します。これらは、
org.graalvm.nativeimage.c.type
パッケージのポインタを含むJavaプリミティブ型です。 - メソッドの一意の名前を指定します。公開された2つのメソッドに同じ名前を付けると、
native-image
ビルダーはduplicate symbol
メッセージで失敗します。注釈で名前を指定しない場合は、ビルド時に-H:Name=libraryName
フラグを指定する必要があります。
次に、エントリポイント・メソッドの例を示します:
@CEntryPoint(name = "function_name")
static int add(IsolateThread thread, int a, int b) {
return a + b;
}
native-image
ツールがネイティブ共有ライブラリをビルドすると、Cヘッダー・ファイルも生成されます。ヘッダー・ファイルには、ネイティブ・イメージC API (Cコードから分離を作成し、スレッドをアタッチできるようにする)の宣言と、共有ライブラリ内の各エントリポイントの宣言が含まれています。native-image
ツールは、前述の例について次のC宣言を含むCヘッダー・ファイルを生成します:
int add(graal_isolatethread_t* thread, int a, int b);
ネイティブ共有ライブラリでは、コールバックやAPIを実装するためなど、エントリポイントを無制限に指定できます。
デモの実行
次の例では、小さいJavaクラス・ライブラリ(1つのクラスを含む)を作成し、native-image
を使用してクラス・ライブラリから共有ライブラリを作成し、共有ライブラリを使用する小さいCアプリケーションを作成します。Cアプリケーションは、文字列を引数として取り、それを共有ライブラリに渡し、引数を含む環境変数を出力します。
前提条件
JAVA_HOME
環境変数をGraalVMインストールの場所に設定しました。
次のように、LLVMツールチェーンのサポートをGraalVMにインストールしました:
$JAVA_HOME/bin/gu install llvm-toolchain
ノート: llvm-toolchain GraalVMコンポーネントはMicrosoft Windowsでは使用できません。
-
次のJavaコードをLibEnvMap.javaという名前のファイルに保存します:
import java.util.Map; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; public class LibEnvMap { //NOTE: this class has no main() method @CEntryPoint(name = "filter_env") private static int filterEnv(IsolateThread thread, CCharPointer cFilter) { String filter = CTypeConversion.toJavaString(cFilter); Map<String, String> env = System.getenv(); int count = 0; for (String envName : env.keySet()) { if(!envName.contains(filter)) continue; System.out.format("%s=%s%n", envName, env.get(envName)); count++; } return count; } }
メソッド
filterEnv()
が@CEntryPoint
注釈を使用してエントリポイントとして識別され、メソッドに注釈の引数として名前が付けられていることに注意してください。 -
次のように、Javaコードをコンパイルし、ネイティブ共有ライブラリをビルドします:
$JAVA_HOME/bin/javac LibEnvMap.java $JAVA_HOME/bin/native-image -H:Name=libenvmap --shared
次のアーティファクトが生成されます:
-------------------------------------------------- Produced artifacts: /demo/libenvmap.dylib (shared_lib) /demo/libenvmap.h (header) /demo/graal_isolate.h (header) /demo/libenvmap_dynamic.h (header) /demo/graal_isolate_dynamic.h (header) /demo/libenvmap.build_artifacts.txt ==================================================
CまたはC++を使用する場合は、これらのヘッダー・ファイルを直接使用します。Javaなどの他の言語では、ヘッダー内の関数宣言を使用して、外部コール・バインディングを設定します。
-
次のコードを含む同じディレクトリに、Cアプリケーションmain.cを作成します:
#include <stdio.h> #include <stdlib.h> #include "libenvmap.h" int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: %s <filter>\n", argv[0]); exit(1); } graal_isolate_t *isolate = NULL; graal_isolatethread_t *thread = NULL; if (graal_create_isolate(NULL, &isolate, &thread) != 0) { fprintf(stderr, "initialization error\n"); return 1; } printf("Number of entries: %d\n", filter_env(thread, argv[1])); graal_tear_down_isolate(thread); }
文
#include "libenvmap.h"
は、ネイティブ共有ライブラリをロードします。 -
clang
を使用して、Cアプリケーションをコンパイルします。$JAVA_HOME/languages/llvm/native/bin/clang -I ./ -L ./ -l envmap -Wl,-rpath ./ -o main main.c
-
文字列を引数として渡して、Cアプリケーションを実行します。たとえば:
./main USER
一致する環境変数の名前および値が正しく出力されます。
ネイティブ・イメージC APIを使用する利点は、APIの外観を決定できることです。パラメータおよび戻り型は非オブジェクト型でなければならないという制限があります。CからJavaオブジェクトを管理する場合は、JNI呼出しAPIを検討する必要があります。
関連ドキュメント
- Truffle言語の埋込み - Kevin Menardによるブログ投稿で、Javaメソッドを公開するための両方のメカニズムを比較しています。
- ネイティブ・コードとの相互運用性
- ネイティブ・イメージのJava Native Interface (JNI)
- ネイティブ・イメージC API