S-JIS[2024-09-23] 変更履歴
Javaの外部関数およびメモリーAPIのMemoryLayoutのメモ。
|
MemoryLayoutは、メモリーを構造体などの形式で扱えるようにするインターフェース。
import java.lang.foreign.MemoryLayout;
「構造体を返す関数」を呼び出す例。
struct MyStruct1 { int value1; // 32bit int value2; // 32bit }
// MyStructを返す関数 MyStruct1 create_my_struct1(int, int);
まず、構造体をJavaで扱う為に、MemoryLayoutを使って構造を定義する。
import static java.lang.foreign.ValueLayout.JAVA_INT; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemoryLayout.PathElement; import java.lang.foreign.StructLayout; import java.lang.invoke.VarHandle;
/** MyStruct1 */ static final StructLayout MY_STRUCT1_LAYOUT = MemoryLayout.structLayout( // JAVA_INT.withName("value1"), // JAVA_INT.withName("value2") // ).withName("MyStruct1");
// 構造体のメンバーにアクセスする為のハンドル static final VarHandle VALUE1_HANDLE = MY_STRUCT1_LAYOUT.varHandle(PathElement.groupElement("value1")); static final VarHandle VALUE2_HANDLE = MY_STRUCT1_LAYOUT.varHandle(PathElement.groupElement("value2"));
varHandleメソッドに構造体のメンバーを指定してVarHandleを取得する。
もし構造体がネストしている場合は、varHandleメソッドは可変引数なので、ネストしている個数分のPathElementを渡すのだと思う。
では、MyStruct1を返す関数を呼び出してみる。
import java.lang.foreign.MemorySegment; import java.lang.invoke.MethodHandle;
try (var arena = Arena.ofConfined()) { // 関数を取得 MemorySegment methodName = symbolLookup.findOrThrow("create_my_struct1"); MethodHandle createMyStruct1 = linker.downcallHandle(methodName, FunctionDescriptor.of(MY_STRUCT1_LAYOUT, JAVA_INT, JAVA_INT)); // System.out.println("create_my_struct1=" + createMyStruct1); // 関数呼び出し var myStruct1memory = (MemorySegment) createMyStruct1.invoke(arena, 123, 456); // 構造体のメンバーの値を確認 // System.out.println("VALUE1_HANDLE=" + VALUE1_HANDLE); // System.out.println("VALUE2_HANDLE=" + VALUE2_HANDLE); int value1 = (int) VALUE1_HANDLE.get(myStruct1memory, 0); int value2 = (int) VALUE2_HANDLE.get(myStruct1memory, 0); System.out.println(value1); System.out.println(value2); }
FunctionDescriptor.of()で、呼び出す関数の返り値と引数の型を定義する。
第1引数が呼び出す関数の返り値の型なので、MyStruct1構造体の定義であるMY_STRUCT1_LAYOUTを渡している。
第2引数以降は呼び出す関数の引数。
MethodHandleのinvokeで実際に関数を呼び出す。
invokeの引数には、通常は、呼び出す関数の引数だけ渡せばいいのだが、
構造体が返る場合は、その構造体の分のメモリーを確保するオブジェクト(arena)を渡す必要があるようだ。
arenaが必要かどうかは、MethodHandleインスタンスをデバッグ出力して見ると分かる。
create_my_struct1=MethodHandle(SegmentAllocator,int,int)MemorySegment
MethodHandle()
の丸括弧内がinvokeに渡す引数を表しているようだ。
SegmentAllocatorはメモリーを確保する為のインターフェースで、ArenaはSegmentAllocatorを継承しているので、invoke時にArenaを渡せばいい。
なお、MethodHandle()
の丸括弧の後ろは、invokeしたときに返ってくる型を表していると思われる。
最後に、VarHandle(VALUE1_HANDLEやVALUE2_HANDLE)を使って構造体のメンバーから値を取得する。
getメソッドの第1引数には構造体の実体を渡す。
第2引数はよく分からないけどindexっぽい気がするので、0を指定しておく。
VarHandleもデバッグ出力すると、getメソッドに渡す型が分かる。
VALUE1_HANDLE=VarHandle[varType=int, coord=[interface java.lang.foreign.MemorySegment, long]] VALUE2_HANDLE=VarHandle[varType=int, coord=[interface java.lang.foreign.MemorySegment, long]]
varTypeがメンバー(データ)の型で、coordがget時に指定する引数の型だと思う。
「構造体のポインターを受け取って構造体のメンバーに値をセットする関数」を呼び出す例。
struct MyStruct1 { int value1; // 32bit int value2; // 32bit }
void set_my_struct1(&MyStruct1, int, int);
構造体のJava側の定義は先の例と同じ。
import static java.lang.foreign.ValueLayout.JAVA_INT; import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.VarHandle;
try (var arena = Arena.ofConfined()) { // 関数を取得 MemorySegment methodName = symbolLookup.findOrThrow("set_my_struct1"); MethodHandle setMyStruct1 = linker.downcallHandle(methodName, FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, JAVA_INT, JAVA_INT)); // System.out.println("set_my_struct1=" + setMyStruct1); // Java側で構造体のメモリーを確保 MemorySegment myStruct1memory = arena.allocate(MY_STRUCT1_LAYOUT); // 関数呼び出し setMyStruct1.invoke(myStruct1memory, 12, 34); // 構造体のメンバーにセットされた値を確認 int value1 = (int) VALUE1_HANDLE.get(myStruct1memory, 0); int value2 = (int) VALUE2_HANDLE.get(myStruct1memory, 0); System.out.println(value1); System.out.println(value2); }
返り値が無い(返り値の型がvoid)の関数を定義する際は、FunctionDescriptor.ofVoid()を使う。
そして、ポインターを渡すときはValueLayout.ADDRESSを指定する。
invokeの方は、ポインターを渡す箇所で素直にMemorySegmentを渡せばいい。