jextractを使用したネイティブ関数のコール

jextractツールは、ネイティブ・ライブラリのヘッダー・ファイルからJavaバインディングを機械的に生成します。このツールが生成するバインディングは、外部関数およびメモリー(FFM) APIに依存します。このツールを使用すると、呼び出す関数のダウンコール・ハンドルおよびアップコール・ハンドルを作成する必要はありません。jextractツールによって、そのためのコードが生成されます。

次のサイトからツールを入手します:

https://jdk.java.net/jextract/

jextractのソース・コードは次のサイトで入手します:

https://github.com/openjdk/jextract

このサイトには、jextractをコンパイルおよび実行する方法のステップ、その他のドキュメントおよびサンプルも含まれています。

JavaアプリケーションでのPythonスクリプトの実行

次のステップでは、Pythonヘッダー・ファイルPython.hからJavaバインディングを生成し、生成されたコードを使用してJavaアプリケーションでPythonスクリプトを実行する方法を示します。このPythonスクリプトは、Java文字列の長さを出力します。

  1. 次のコマンドを実行して、Python.hのJavaバインディングを生成します:
    jextract -l <absolute path of Python shared library> \
      --output classes \
      -I <directory containing Python header files> \
      -t org.python <absolute path of Python.h>

    たとえば:

    jextract -l /lib64/libpython3.6m.so.1.0 \
      --output classes \
      -I /usr/include/python3.6m \
      -t org.python /usr/include/python3.6m/Python.h 

    ヒント:

    • Linuxシステムで、Python共有ライブラリのファイル名を取得するには、次のコマンドを実行します。この例では、システムにPython 3がインストールされていることを前提としています。

      ldd $(which python3)

      このコマンドを実行すると、次のような出力が表示されます:

              linux-vdso.so.1 =>  (0x00007ffdb4bd5000)
              libpython3.6m.so.1.0 => /lib64/libpython3.6m.so.1.0 (0x00007fb0386a7000)
              libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb03848b000)
              libdl.so.2 => /lib64/libdl.so.2 (0x00007fb038287000)
              libutil.so.1 => /lib64/libutil.so.1 (0x00007fb038084000)
              libm.so.6 => /lib64/libm.so.6 (0x00007fb037d82000)
              libc.so.6 => /lib64/libc.so.6 (0x00007fb0379b4000)
              /lib64/ld-linux-x86-64.so.2 (0x00007fb038bce000)
    • Linuxシステムで、Python.hまたはPythonヘッダー・ファイルを含むディレクトリが見つからない場合は、python-develパッケージをインストールする必要がある場合があります。

    • jextractツールによって作成されるクラスおよびメソッドを調べる場合は、--sourceオプションを指定してコマンドを実行します。たとえば、次のコマンドによって、Python.hのJavaバインディングのソース・ファイルが生成されます:
      jextract --source \
        --output src \
        -I <directory containing Python header files> \
        -t org.python <absolute path of Python.h>
  2. classesと同じディレクトリ(Python Javaバインディングが含まれる)に、次のファイルPythonMain.javaを作成します:
    import java.lang.foreign.Arena;
    import java.lang.foreign.MemorySegment;
    import static java.lang.foreign.MemorySegment.NULL;
    import static org.python.Python_h.*;
    
    public class PythonMain {
        
        public static void main(String[] args) {
            String myString = "Hello world!";
            String script = """
                         string = "%s"
                         print(string, ': ', len(string), sep='')
                         """.formatted(myString).stripIndent();
            Py_Initialize();
            
            try (Arena arena = Arena.ofConfined()) {
                MemorySegment nativeString = arena.allocateUtf8String(script);
                PyRun_SimpleStringFlags(
                    nativeString,
                    NULL);
                Py_Finalize();
            }
            Py_Exit(0);
        }
    }
  3. 次のコマンドを使用して、PythonMain.javaをコンパイルします:
    javac --enable-preview -source 21 -cp classes PythonMain.java
  4. 次のコマンドを使用して、PythonMainを実行します:
    java --enable-preview -cp classes:. --enable-native-access=ALL-UNNAMED PythonMain

Javaアプリケーションからのqsort関数のコール

前述のように、qsortは、2つの要素を比較する関数へのポインタを必要とするCライブラリ関数です。次のステップでは、jextractを使用してC標準ライブラリのJavaバインディングを作成し、qsortで必要な比較関数のアップコール・ハンドルを作成し、qsort関数をコールします。

  1. 次のコマンドを実行して、C標準ライブラリのヘッダー・ファイルであるstdlib.hのJavaバインディングを作成します:
    jextract --output classes -t org.unix <absolute path to stdlib.h>

    たとえば:

    jextract --output classes -t org.unix /usr/include/stdlib.h

    stdlib.hに対して生成されるJavaバインディングに含まれるのは、stdlib_hという名前のJavaクラス(qsort(MemorySegment, long, long, MemorySegment)という名前のJavaメソッドが含まれる)と、__compar_fn_tという名前のJavaインタフェース(allocateという名前のメソッドが含まれる)です。このメソッドによって、qsort関数で必要な比較関数の関数ポインタが作成されます。jextractによって生成されるJavaバインディングのソース・コードを調べるには、--sourceオプションを指定してツールを実行します:

    jextract --source --output src -t org.unix <absolute path to stdlib.h>
  2. stdlib.hのJavaバインディングを生成したディレクトリと同じディレクトリに、次のJavaソース・ファイルQsortMain.javaを作成します:
    import static org.unix.__compar_fn_t.*;
    import static org.unix.stdlib_h.*;
    import java.lang.foreign.Arena;
    import java.lang.foreign.MemorySegment;
    import java.lang.foreign.ValueLayout;
    
    public class QsortMain {
        
        public static void main(String[] args) {
            
            int[] unsortedArray = new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 };
    
            try (Arena a = Arena.ofConfined()) {
                
                // Allocate off-heap memory and store unsortedArray in it
                MemorySegment array = a.allocateArray(
                    ValueLayout.JAVA_INT,
                    unsortedArray);            
    
                // Create upcall for comparison function
                //
                // MemorySegment allocate(__compar_fn_t, Arena) is from
                // __compar_fn-t.java, generated by jextract
                
                MemorySegment comparFunc = allocate(
                    (addr1, addr2) ->
                        Integer.compare(
                            addr1.get(ValueLayout.JAVA_INT, 0),
                            addr2.get(ValueLayout.JAVA_INT, 0)),
                        a);
               
                // Call qsort
                qsort(array, (long) unsortedArray.length,
                    ValueLayout.JAVA_INT.byteSize(), comparFunc);      
    
                // Deference off-heap memory
                int[] sortedArray = array.toArray(ValueLayout.JAVA_INT);
    
                for (int num : sortedArray) {
                    System.out.print(num + " ");
                }
                System.out.println();        
            } 
        }
    }

    次の文によって、ラムダ式からアップコールcomparFuncが作成されます:

                // Create upcall for comparison function
                //
                // MemorySegment allocate(__compar_fn_t, SegmentScope) is from
                // __compar_fn-t.java, generated by jextract
                
                MemorySegment comparFunc = allocate(
                    (addr1, addr2) ->
                        Integer.compare(
                            addr1.get(ValueLayout.JAVA_INT, 0),
                            addr2.get(ValueLayout.JAVA_INT, 0)),
                        a);

    したがって、アップコール: Javaコードを関数ポインタとして外部関数に渡すで説明されているように、比較関数のメソッド・ハンドルを作成する必要はありません。

  3. 次のコマンドを使用して、QsortMain.javaをコンパイルします:
    javac --enable-preview -source 21 -cp classes QsortMain.java
  4. 次のコマンドを使用して、QsortMainを実行します:
    java --enable-preview -cp classes:. --enable-native-access=ALL-UNNAMED QsortMain