アップコール: 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という名前の比較メソッドを定義し、この比較メソッドを表すメソッド・ハンドルを作成し、このメソッド・ハンドルの関数ポインタを作成します。

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();
    
    // Create downcall handle for qsort
    static final MethodHandle qsort = linker.downcallHandle(
        linker.defaultLookup().findOrThrow("qsort"),
        FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
                                  ValueLayout.JAVA_LONG,
                                  ValueLayout.JAVA_LONG,
                                  ValueLayout.ADDRESS));
                                  
    // Create method handle for qsortCompare
    static final MethodHandle compareHandle = initCompareHandle();
                                  
    static MethodHandle initCompareHandle() {
        MethodHandle ch = null;
        try {
            ch = MethodHandles.lookup()
                .findStatic(Qsort.class,
                        "qsortCompare",
                        MethodType.methodType(int.class,
                                              MemorySegment.class,
                                              MemorySegment.class));
        } catch (NoSuchMethodException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return ch;
    }
    
    static int[] qsortTest(int[] unsortedArray) throws Throwable {
        
        int[] sorted = null;
                                              
        // 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(compareHandle,
                                                      qsortCompareDesc,
                                                      Arena.ofAuto());        
        
        try (Arena arena = Arena.ofConfined()) {                    
        
            // Allocate off-heap memory and store unsortedArray in it                
            MemorySegment array = arena.allocateFrom(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();
    
// Create downcall handle for qsort
static final MethodHandle qsort = linker.downcallHandle(
    linker.defaultLookup().findOrThrow("qsort"),
    FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
                              ValueLayout.JAVA_LONG,
                              ValueLayout.JAVA_LONG,
                              ValueLayout.ADDRESS));

ヒント:

パフォーマンス上の理由から、メソッド・ハンドルをstatic finalとして宣言することをお薦めします。

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

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

// Create method handle for qsortCompare
static final MethodHandle compareHandle = initCompareHandle();
                                  
static MethodHandle initCompareHandle() {
    MethodHandle ch = null;
    try {
        ch = MethodHandles.lookup()
            .findStatic(Qsort.class,
                    "qsortCompare",
                    MethodType.methodType(int.class,
                                          MemorySegment.class,
                                          MemorySegment.class));
    } catch (NoSuchMethodException | IllegalAccessException e) {
        e.printStackTrace();
    }
    return ch;
}

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(compareHandle,
                                              qsortCompareDesc,
                                              Arena.ofAuto());

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

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

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

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

int[] sorted = null;
// ...
try (Arena arena = Arena.ofConfined()) {                    
       
    // Allocate off-heap memory and store unsortedArray in it                
    MemorySegment array = arena.allocateFrom(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);