S-JIS[2024-09-23/2024-09-25] 変更履歴
Javaの外部関数およびメモリーAPIのメモ。
|
|
|
Java22(プレビュー版ではJava19)で、外部関数およびメモリーAPIが導入された。
これにより、(JNIを使用せずに)OSネイティブのライブラリーの関数を呼ぶことが出来る。
自分で作った「int add(int, int)
」という関数を呼び出す例。
import static java.lang.foreign.ValueLayout.JAVA_INT; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; import java.lang.foreign.MemorySegment; import java.lang.foreign.SymbolLookup; import java.lang.invoke.MethodHandle;
public class FfiExample { static { System.loadLibrary("ffi_example_rust"); }
public static void main(String... args) throws Throwable { SymbolLookup symbolLookup = SymbolLookup.loaderLookup(); MemorySegment addSegment = symbolLookup.find("add").orElseThrow(); Linker linker = Linker.nativeLinker(); MethodHandle addFunction = linker.downcallHandle(addSegment, FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT)); int r = (int) addFunction.invoke(1, 2); System.out.println(r); } }
この例では、System.loadLibrary()を使ってライブラリーファイル(呼び出したい関数が定義されている)を読み込んでいる。
System.loadLibrary()のライブラリーを対象とする場合はSymbolLookup.loaderLookup()でSymbolLookupインスタンスを取得する。
呼び出したい関数のポインターは、シンボル名(関数名)を指定してSymbolLookupから取得する。
そして、関数を呼び出すためのハンドル(MethodHandle)をLinkerから取得する。
FunctionDescriptor.of()の第1引数は関数の戻り値の型で、第2引数以降が関数の引数の型。
MethodHandleのinvoke()やinvokeExact()で関数を呼び出す。
invokeExact()は引数や戻り値の型の厳密なチェックを行う。
invoke()の方は柔軟な型チェックであり、例えばintにIntegerを渡しても変換してくれるそうだ。変換が入る分、invokeExact()より実行速度が遅いらしい。
今回の例ではSystem.loadLibrary()を使っているので、このクラスを実行する為にはライブラリーファイルの場所を指定してやる必要がある。
javaコマンドの-Djava.library.path
オプションか、環境変数(Linuxの場合はLD_LIBRARY_PATH、Windowsの場合はPATH)でライブラリーファイルのディレクトリーを指定する。
> java -Djava.library.path=C:/example/ffi/ffi-example-rust/target/release com.example.ffi.FfiExample
linker.downcallHandle()を実行した際に、以下のような警告メッセージが出ることがある。
WARNING: A restricted method in java.lang.foreign.Linker has been called WARNING: java.lang.foreign.Linker::downcallHandle has been called by com.example.ffi.FfiExample in an unnamed module WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module WARNING: Restricted methods will be blocked in a future release unless native access is enabled
ネイティブライブラリーを呼び出すのはセキュリティー的に危険なので、きちんと「--enable-native-access」を指定しろ、ということらしい。
(今は警告が出るだけで実行できるけど、将来的には実行できなくなるかもしれない)
このメッセージの通り、「--enable-native-access」を指定すれば警告は出なくなる。
> java --enable-native-access=ALL-UNNAMED 〜 com.example.ffi.FfiExample
が、ALL-UNNAMEDはモジュールが定義されていない全てのコードでネイティブライブラリーの呼び出しを許可するというものなので、指定する範囲が広すぎる。
きちんとモジュールを定義すれば、そのモジュール名だけを指定できる。
module com.example.ffi { }
> java --enable-native-access=com.example.ffi 〜 com.example.ffi.FfiExample
なお、module-info.javaでモジュール名を定義していて、実行時に「--enable-native-access」があるけど自分のモジュール名が指定されていない場合は、以下のようなエラーになる。
> java --enable-native-access=hoge 〜 com.example.ffi.FfiExample
WARNING: Unknown module: hoge specified to --enable-native-access
Exception in thread "main" java.lang.IllegalCallerException: Illegal native access from: module com.example.ffi
at java.base/java.lang.Module.ensureNativeAccess(Module.java:312)
at java.base/java.lang.System$2.ensureNativeAccess(System.java:2543)
at java.base/jdk.internal.reflect.Reflection.ensureNativeAccess(Reflection.java:122)
at java.base/jdk.internal.foreign.abi.AbstractLinker.downcallHandle(AbstractLinker.java:83)
at com.example.ffi/com.example.ffi.FfiExample.main(FfiExample.java:23)