戻る

ネイティブ共有ライブラリの構築

ネイティブ共有ライブラリを構築するには、コマンドライン引数--sharednative-imageツールに次のように渡します

native-image <class name> --shared

JARファイルからネイティブ共有ライブラリを構築するには、次の構文を使用します

native-image -jar <jarfile> --shared

結果のネイティブ共有ライブラリには、指定されたJavaクラスのmain()メソッドがエントリポイントメソッドとして含まれます。

ライブラリにmain()メソッドが含まれていない場合は、-oコマンドラインオプションを使用して、次のようにライブラリ名を指定します

native-image --shared -o <libraryname> <class name>
native-image --shared -jar <jarfile> -o <libraryname>

GraalVMを使用すると、Cを使用してネイティブ共有ライブラリを簡単に呼び出すことができます。ネイティブ共有ライブラリに埋め込まれたメソッド(関数)を呼び出すための主なメカニズムは2つあります。1つはNative Image C API、もう1つはJNI Invocation APIです。

このガイドでは、Native Image C APIを使用する方法について説明します。手順は次のとおりです

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

ヒントとコツ

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

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

  • メソッドをstaticとして宣言します。
  • メソッドに@CEntryPoint(org.graalvm.nativeimage.c.function.CEntryPoint)アノテーションを付けます。
  • メソッドのパラメータの1つをIsolateThreadまたはIsolate型にします。たとえば、次のメソッドの最初のパラメータ(org.graalvm.nativeimage.IsolateThread)のようにします。このパラメータは、呼び出しに対して現在のスレッドの実行コンテキストを提供します。
  • パラメータと戻り値の型を非オブジェクト型に制限します。これらは、org.graalvm.nativeimage.c.typeパッケージからのポインタを含むJavaプリミティブ型です。
  • メソッドに一意の名前を付けます。2つの公開されたメソッドに同じ名前を付けると、native-imageビルダーはduplicate symbolメッセージで失敗します。アノテーションで名前を指定しない場合は、ビルド時に-o <libraryName>オプションを指定する必要があります。

以下は、エントリポイントメソッドの例です

@CEntryPoint(name = "function_name")
static int add(IsolateThread thread, int a, int b) {
    return a + b;
}

native-imageツールがネイティブ共有ライブラリをビルドすると、Cヘッダーファイルも生成されます。ヘッダーファイルには、Native Image C API(Cコードからアイソレートを作成してスレッドをアタッチできるようにします)の宣言と、共有ライブラリの各エントリポイントの宣言が含まれています。以下は、上記の例のCヘッダー宣言です

int add(graal_isolatethread_t* thread, int a, int b);

ネイティブ共有ライブラリには、コールバックやAPIを実装するなど、無制限のエントリポイントを含めることができます。

デモの実行

次の例では、小さなJavaクラスライブラリ(1つのクラスを含む)を作成し、native-imageを使用してクラスライブラリから共有ライブラリを作成し、その共有ライブラリを使用する小さなCアプリケーションを作成します。Cアプリケーションは引数としてStringを受け取り、それを共有ライブラリに渡し、引数を含む環境変数を印刷します。

前提条件

GraalVM JDKがインストールされていることを確認してください。開始する最も簡単な方法はSDKMAN!を使用することです。その他のインストールオプションについては、ダウンロードセクションをご覧ください。

  1. 次の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アノテーションを使用してエントリポイントとして識別され、メソッドにアノテーションへの引数として名前が与えられていることに注意してください。

  2. 次のように、Javaコードをコンパイルしてネイティブ共有ライブラリを構築します
     javac LibEnvMap.java
    
     native-image -o libenvmap --shared 
    

    次のアーティファクトが生成されます

     --------------------------------------------------
     Produced artifacts:
     /demo/graal_isolate.h (header)
     /demo/graal_isolate_dynamic.h (header)
     /demo/libenvmap.dylib (shared_lib)
     /demo/libenvmap.h (header)
     /demo/libenvmap_dynamic.h (header)
     ==================================================
    

    CまたはC++を使用する場合は、これらのヘッダーファイルを直接使用してください。Javaなどの他の言語の場合は、ヘッダー内の関数宣言を使用して、外部呼び出しバインディングを設定します。

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

  4. システムで使用可能なclangコンパイラを使用して、main.cをコンパイルします
     clang -I ./ -L ./ -l envmap -Wl,-rpath ./ -o main main.c 
    

    実行可能ファイルmainが作成されます。

  5. 文字列を引数として渡して、Cアプリケーションを実行します。たとえば
     ./main USER
    

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

Native Image C APIを使用する利点は、APIがどのようなものになるかを判断できることです。制限は、パラメータと戻り値の型が非オブジェクト型である必要があることです。CからJavaオブジェクトを管理する場合は、JNI Invocation APIを検討する必要があります。

つながりましょう