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 <directory containing code generated by jextract> \
      -I <directory containing Python header files> \
      -t org.python <absolute path of Python.h>

    たとえば:

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

    Linuxで、Python共有ライブラリのパスを取得するには、次のコマンドを実行します:

    ldconfig -p | grep libpython

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

            libpython3.6m.so.1.0 (libc6,x86-64) => /lib64/libpython3.6m.so.1.0
            libpython3.6m.so (libc6,x86-64) => /lib64/libpython3.6m.so
            libpython3.so (libc6,x86-64) => /lib64/libpython3.so
            libpython2.7.so.1.0 (libc6,x86-64) => /lib64/libpython2.7.so.1.0
            libpython2.7.so (libc6,x86-64) => /lib64/libpython2.7.so

    -lオプションの値は、生成されたヘッダー・クラスがロードする必要のある共有ライブラリのパスまたは名前です。コロン(:)で始まる場合、値はライブラリ・パスとして解釈されます。それ以外の場合は、libGL.soGLなどのライブラリ名です。

    jextractツールは、動的リンカーによって認識される任意のライブラリ指定子を解決できます。したがって、このコマンドは次のように実行できます。

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

    Linuxシステムで、Python.hまたはPythonヘッダー・ファイルを含むディレクトリが見つからない場合は、python-develパッケージをインストールする必要がある場合があります。

  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.allocateFrom(script);
                PyRun_SimpleStringFlags(
                    nativeString,
                    NULL);
                Py_Finalize();
            }
            Py_Exit(0);
        }
    }
  3. 次のコマンドを使用して、PythonMain.javaをコンパイルします:
    javac -sourcepath gensrc PythonMain.java
  4. 次のコマンドを使用して、PythonMainを実行します:
    java -cp gensrc:. --enable-native-access=ALL-UNNAMED PythonMain

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

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

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

    たとえば:

    jextract --output gensrc -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関数で必要な比較関数の関数ポインタが作成されます。

  2. stdlib.hのJavaバインディングを生成したディレクトリと同じディレクトリに、次のJavaソース・ファイルQsortMain.javaを作成します:
    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
                //
                // stdlib_h.C_INT is a constant generated by jextract
                
                MemorySegment array = a.allocateFrom(C_INT, unsortedArray);            
    
                // Create upcall stub for the comparison function
                //
                // MemorySegment org.unix.__compar_fn_t.allocate(__compar_fn_t, Arena)
                // is from __compar_fn_t.java, generated by jextract
                
                MemorySegment comparFunc = org.unix.__compar_fn_t.allocate(
                    (addr1, addr2) ->
                        Integer.compare(
                            addr1.get(C_INT, 0),
                            addr2.get(C_INT, 0)),
                        a);
               
                // Call qsort
                qsort(array, (long) unsortedArray.length, C_INT.byteSize(), comparFunc);      
    
                // Copy off-heap memory into new int[]
                int[] sortedArray = array.toArray(C_INT);
    
                for (int num : sortedArray) {
                    System.out.print(num + " ");
                }
                System.out.println();        
            } 
        }
    }

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

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

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

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