外部関数およびメモリーAPIを使用したCライブラリ関数のコール

次の例は、外部関数およびメモリーAPIを使用してstrlenをコールしています:

    static long invokeStrlen(String s) throws Throwable {
        
        try (Arena arena = Arena.ofConfined()) {
            
            // Allocate off-heap memory and
            // copy the argument, a Java string, into off-heap memory
            MemorySegment nativeString = arena.allocateUtf8String(s);
        
            // Link and call the C function strlen
        
            // Obtain an instance of the native linker
            Linker linker = Linker.nativeLinker();
        
            // Locate the address of the C function signature
            SymbolLookup stdLib = linker.defaultLookup();
            MemorySegment strlen_addr = stdLib.find("strlen").get();
        
            // Create a description of the C function
            FunctionDescriptor strlen_sig =
                FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
            
            // Create a downcall handle for the C function    
            MethodHandle strlen = linker.downcallHandle(strlen_addr, strlen_sig);

            // Call the C function directly from Java
            return (long)strlen.invokeExact(nativeString);
        } 
    }

strlen C標準ライブラリ関数の宣言を次に示します:

size_t strlen(const char *s);

これは1つの引数(文字列)を取り、その文字列の長さを返します。Javaアプリケーションからこの関数を呼び出すには、次のステップに従います:

  1. strlen関数の引数のために、オフヒープ・メモリー (Javaランタイム外部のメモリー)を割り当てます。

  2. Java文字列を、割り当てたオフヒープ・メモリーに格納します。

    invokeStrlenの例では、前のステップとこのステップを次の文で実行します:

    MemorySegment nativeString = arena.allocateUtf8String(s);
  3. strlen関数を指すメソッド・ハンドルを作成して呼び出します。この項のトピックでその方法を説明します。

次の各項で、この例について詳しく説明します。

ネイティブ・リンカーのインスタンスの取得

次の文は、Javaランタイムが実行されているプラットフォームの呼出し規則に準拠したライブラリへのアクセスを提供する、ネイティブ・リンカーのインスタンスを取得します。これらのライブラリはネイティブ・ライブラリと呼ばれます。

            Linker linker = Linker.nativeLinker();

C関数のアドレスの確認

ネイティブ・メソッド(strlenなど)をコールするには、ネイティブ関数を指すMethodHandleインスタンスであるダウンコール・メソッド・ハンドルが必要です。このインスタンスには、ネイティブ関数のアドレスが必要です。

次の文によって、strlen関数のアドレスが取得されます:

            SymbolLookup stdLib = SymbolLookup.libraryLookup("libc.so.6", arena);
            MemorySegment strlen_addr = stdLib.find("strlen").get();

SymbolLookup.libraryLookup(String, Arena)によって、ユーザー指定のネイティブ・ライブラリ内のすべてのシンボルを検索するライブラリ検索が作成されます。ネイティブ・ライブラリをロードし、ライブラリ検索の存続期間を制御するアリーナに関連付けます。この例で、libc.so.6は、多くのLinuxシステムでのC標準ライブラリのファイル名です。

strlenはC標準ライブラリに含まれるため、ネイティブ・リンカーのデフォルト検索をかわりに使用できます。これは、よく使用される一連のライブラリ(C標準ライブラリを含む)内のシンボルを検索するものです。つまり、システムによって異なるライブラリ・ファイル名を指定する必要がありません:

            // Obtain an instance of the C function
            Linker linker = Linker.nativeLinker();
        
            // Locate the address of the C function
            SymbolLookup stdLib = linker.defaultLookup();
            MemorySegment strlen_addr = stdLib.find("strlen").get();

ヒント:

SymbolLookup.loaderLookup()をコールして、System.loadLibrary(String)でロードされるライブラリでシンボルを探します。

C関数シグネチャの説明

ダウンコール・メソッド・ハンドルには、ネイティブ関数のシグネチャの説明も必要です。これはFunctionDescriptorインスタンスによって表されます。関数ディスクリプタは、ネイティブ関数の引数のレイアウトとその戻り値(ある場合)を記述するものです。

関数ディスクリプタ内の各レイアウトはJava型にマップされます。これは、結果のダウンコール・メソッド・ハンドルを呼び出すときに使用する型です。ほとんどの値レイアウトは、Javaプリミティブ型にマップされます。たとえば、ValueLayout.JAVA_INTint値にマップされます。ただし、ValueLayout.ADDRESSはポインタにマップされます。

struct型やunionなどの複合型は、GroupLayoutインタフェースでモデル化されます。これはStructLayoutおよびUnionLayoutのスーパータイプです。C構造体の初期化およびアクセス方法の例は、 「メモリー・レイアウトおよび構造化アクセス」を参照してください。

次によって、strlen関数の関数ディスクリプタが作成されます:

            // Create a description of the C function signature
            FunctionDescriptor strlen_sig =
                FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);

FunctionDescriptor::ofメソッドの最初の引数は、ネイティブ関数の戻り値のレイアウトです。ネイティブ・プリミティブ型は、それらの型とサイズが一致する値レイアウトを使用してモデル化されます。つまり、関数ディスクリプタはプラットフォーム固有です。たとえば、size_tは64ビットまたはx64プラットフォームではJAVA_LONGのレイアウトですが、32ビットまたはx86プラットフォームではJAVA_INTのレイアウトです。

FunctionDescriptor::ofの後続の引数は、ネイティブ関数の引数のレイアウトです。この例では、後続の引数はValueLayout.ADDRESSのみです。これは、strlenの唯一の引数(文字列へのポインタ)を表します。

C関数のダウンコール・ハンドルの作成

次の文では、strlen関数のアドレスと関数ディスクリプタを使用して、この関数のダウンコール・メソッド・ハンドルが作成されます。

            // Create a downcall handle for the C function    
            MethodHandle strlen = linker.downcallHandle(strlen_addr, strlen_sig);  

JavaからのC関数の直接コール

次の文では、strlen関数の引数を含むメモリー・セグメントを使用してこの関数がコールされます:

            // Call the C function directly from Java
            return (long)strlen.invokeExact(nativeString);

予期される戻り型(この場合はlong)を指定してメソッド・ハンドル呼出しをキャストする必要があります。