S-JIS[2024-09-23/2024-09-25] 変更履歴

Java外部関数およびメモリーAPI

Javaの外部関数およびメモリーAPIのメモ。

  • jextract [/2024-09-25]
    (C言語のヘッダーファイルから当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はモジュールが定義されていない全てのコードでネイティブライブラリーの呼び出しを許可するというものなので、指定する範囲が広すぎる。


きちんとモジュールを定義すれば、そのモジュール名だけを指定できる。

src/main/java/module-info.java:

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)

Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま