アップコール: Javaコードを関数ポインタとして外部関数に渡す

アップコールは、ネイティブ・コードからJavaコードに戻すコールです。アップコール・スタブによってJavaコードを関数ポインタとして外部関数に渡すことができます。

配列の要素をソートする、標準Cライブラリ関数qsortについて考えてみます:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

これは4つの引数を取ります:

  • base: ソート対象の配列の最初の要素へのポインタ
  • nbemb: 配列の要素数
  • size: 配列内の各要素のサイズ(バイト)
  • compar: 2つの要素を比較する関数へのポインタ

次の例では、qsort関数をコールしてint配列をソートします。ただし、このメソッドには2つの配列要素を比較する関数へのポインタが必要です。この例では、Qsort::qsortCompareという名前の比較メソッドを定義し、この比較メソッドを表すメソッド・ハンドルを作成し、このメソッド・ハンドルの関数ポインタを作成します。

import java.lang.foreign.*;
import java.lang.invoke.*;
import java.lang.foreign.ValueLayout.*;

public class InvokeQsort {
        
    class Qsort {
        static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
            return Integer.compare(elem1.get(ValueLayout.JAVA_INT, 0), elem2.get(ValueLayout.JAVA_INT, 0));
        }
    }
    
    // Obtain instance of native linker
    final static Linker linker = Linker.nativeLinker();
    
    static int[] qsortTest(int[] unsortedArray) throws Throwable {
        
        int[] sorted = null;
        
        // Create downcall handle for qsort
        MethodHandle qsort = linker.downcallHandle(
            linker.defaultLookup().find("qsort").get(),
            FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
                                      ValueLayout.JAVA_LONG,
                                      ValueLayout.JAVA_LONG,
                                      ValueLayout.ADDRESS));
        
        // Create method handle for qsortCompare
        MethodHandle comparHandle = MethodHandles.lookup()
            .findStatic(Qsort.class,
                        "qsortCompare",
                        MethodType.methodType(int.class,
                                              MemorySegment.class,
                                              MemorySegment.class));
                                              
        // Create a Java description of a C function implemented by a Java method
        FunctionDescriptor qsortCompareDesc = FunctionDescriptor.of(
            ValueLayout.JAVA_INT,
            ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
            ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT));


        // Create function pointer for qsortCompare
        MemorySegment compareFunc = linker.upcallStub(comparHandle,
                                                      qsortCompareDesc,
                                                      Arena.ofAuto());        
        
        try (Arena arena = Arena.ofConfined()) {                    
        
            // Allocate off-heap memory and store unsortedArray in it                
            MemorySegment array = arena.allocateArray(ValueLayout.JAVA_INT,
                                                      unsortedArray);        
                    
            // Call qsort        
            qsort.invoke(array,
                        (long)unsortedArray.length,
                        ValueLayout.JAVA_INT.byteSize(),
                        compareFunc);
            
            // Access off-heap memory
            sorted = array.toArray(ValueLayout.JAVA_INT);              
        }
        return sorted;
    }        
    
    public static void main(String[] args) {
        try { 
            int[] sortedArray = InvokeQsort.qsortTest(new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 });
            for (int num : sortedArray) {
                System.out.print(num + " ");
            }
            System.out.println();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

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

2つの要素を比較するJavaメソッドの定義

次のクラスによって、2つの要素(この場合は2つのint値)を比較するJavaメソッドが定義されます:

    class Qsort {
        static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
            return Integer.compare(elem1.get(ValueLayout.JAVA_INT, 0), elem2.get(ValueLayout.JAVA_INT, 0));
        }
    }

このメソッドでは、int値はMemorySegmentオブジェクトによって表されます。メモリー・セグメントを使用すると、メモリーの連続した領域にアクセスできます。メモリー・セグメントの値を取得するには、そのgetメソッドの1つをコールします。この例では、get(ValueLayout.OfInt, long)をコールします。ここで、2番目の引数は、メモリー・アドレスの場所を基準としたオフセット(バイト単位)です。この例のメモリー・セグメントには1つの値しか格納されないため、2番目の引数は0です。

qsort関数のダウンコール・メソッド・ハンドルの作成

次の文によって、qsort関数のダウンコール・メソッド・ハンドルが作成されます:

    // Obtain instance of native linker
    final static Linker linker = Linker.nativeLinker();
    
    static int[] qsortTest(int[] unsortedArray) throws Throwable {
        
        int[] sorted = null;
        
        // Create downcall handle for qsort
        MethodHandle qsort = linker.downcallHandle(
            linker.defaultLookup().find("qsort").get(),
            FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
                                      ValueLayout.JAVA_LONG,
                                      ValueLayout.JAVA_LONG,
                                      ValueLayout.ADDRESS));

比較メソッドqsortCompareを表すメソッド・ハンドルの作成

次の文によって、比較メソッドQsort::qsortCompareを表すメソッド・ハンドルが作成されます:

        // Create method handle for qsortCompare
        MethodHandle comparHandle = MethodHandles.lookup()
            .findStatic(Qsort.class,
                        "qsortCompare",
                        MethodType.methodType(int.class,
                                              MemorySegment.class,
                                              MemorySegment.class));

MethodHandles.Lookup.findStatic(Class, String, MethodType)メソッドは、staticメソッドのメソッド・ハンドルを作成します。これは次の3つの引数を取ります。

  • メソッドのクラス
  • メソッドの名前
  • メソッドの型: MethodType::methodTypeの最初の引数は、メソッドの戻り値の型です。残りは、メソッドの引数の型です。

メソッド・ハンドルcompareHandleからの関数ポインタの作成

次の文によって、メソッド・ハンドルcompareHandleの関数ポインタが作成されます:

        // Create a Java description of a C function implemented by a Java method
        FunctionDescriptor qsortCompareDesc = FunctionDescriptor.of(
            ValueLayout.JAVA_INT,
            ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
            ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT));

        // Create function pointer for qsortCompare
        MemorySegment compareFunc = linker.upcallStub(comparHandle,
                                                      qsortCompareDesc,
                                                      Arena.ofAuto());  

Linker::upcallStubメソッドは3つの引数を取ります:

  • 関数ポインタを作成するメソッド・ハンドル
  • 関数ポインタの関数ディスクリプタ。この例では、FunctionDescriptor.ofの引数はQsort::qsortCompareの戻り値の型と引数に対応しています
  • 関数ポインタに関連付けるアリーナ。静的メソッドArena.ofAuto()は、ガベージ・コレクタによって自動的に管理される新しいアリーナを作成します。

int配列を格納するためのオフヒープ・メモリーの割当て

次の文によって、オフヒープ・メモリーが割り当てられ、ソート対象のint配列がその中に格納されます:

        try (Arena arena = Arena.ofConfined()) {                    
        
            // Allocate off-heap memory and store unsortedArray in it                
            MemorySegment array = arena.allocateArray(ValueLayout.JAVA_INT,
                                                      unsortedArray);

qsort関数の呼出し

次の文によって、qsort関数がコールされます:

            // Call qsort        
            qsort.invoke(array,
                        (long)unsortedArray.length,
                        ValueLayout.JAVA_INT.byteSize(),
                        compareFunc);

この例で、MethodHandle::invokeの引数は、標準Cライブラリqsort関数の引数に対応します。

オフヒープ・メモリーからオンヒープ・メモリーへのソート済配列値のコピー

最後に、次の文によって、ソートされた配列値がオフヒープ・メモリーからオンヒープ・メモリーにコピーされます:

            // Access off-heap memory
            sorted = array.toArray(ValueLayout.JAVA_INT);