アップコール: 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
関数の引数に対応します。