共有ライブラリのビルド

ネイティブ共有ライブラリをビルドするには、次のように、コマンドライン引数--sharednative-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 APIJNI呼出しAPIの2つのメカニズムがあります。

このガイドでは、ネイティブ・イメージC APIを使用する方法について説明します。これには次のステップが含まれます。

  1. 少なくとも1つのエントリポイント・メソッドを含むJavaクラス・ライブラリを作成してコンパイルします。
  2. native-imageツールを使用して、Javaクラス・ライブラリから共有ライブラリを作成します。
  3. 共有ライブラリ内でエントリポイント・メソッドを呼び出すCアプリケーションを作成してコンパイルします。

ヒントとコツ

共有ライブラリには、少なくとも1つのエントリポイント・メソッドが必要です。デフォルトでは、public static void main()メソッドからのmain()というメソッドのみがエントリポイントとして識別され、Cアプリケーションからコールできます。

他のJavaメソッドをエクスポートするには:

次に、エントリポイント・メソッドの例を示します:

@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アプリケーションは、文字列を引数として取り、それを共有ライブラリに渡し、引数を含む環境変数を出力します。

  1. GraalVM JDKがインストール済であることを確認します。最も簡単に始めるには、SDKMAN!を使用します。その他のインストール・オプションについては、「ダウンロード」セクションを参照してください。

  2. 次に、LLVMツールチェーンをインストールします:
     gu install llvm-toolchain
    

    ノート: llvm-toolchain GraalVMコンポーネントはMicrosoft Windowsでは使用できません。

  3. 次の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注釈を使用してエントリポイントとして識別され、メソッドに注釈の引数として名前が付けられていることに注意してください。

  4. 次のように、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などの他の言語では、ヘッダー内の関数宣言を使用して、外部コール・バインディングを設定します。

  5. 次のコードを含む同じディレクトリに、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"は、ネイティブ共有ライブラリをロードします。

  6. clangを使用して、Cアプリケーションをコンパイルします。
     $JAVA_HOME/languages/llvm/native/bin/clang -I ./ -L ./ -l envmap -Wl,-rpath ./ -o main main.c 
    
  7. 文字列を引数として渡して、Cアプリケーションを実行します。たとえば:
     ./main USER
    

    一致する環境変数の名前および値が正しく出力されます。

ネイティブ・イメージC APIを使用する利点は、APIの外観を決定できることです。パラメータおよび戻り型は非オブジェクト型でなければならないという制限があります。CからJavaオブジェクトを管理する場合は、JNI呼出しAPIを検討する必要があります。