errnoを使用したネイティブ・エラーの確認

一部のC標準ライブラリ関数は、C標準ライブラリマクロerrnoの値を設定することによってエラーを示します。この値にはFFM APIリンカー・オプションを使用してアクセスできます。

Linker::downcallHandleメソッドには、追加のリンカー・オプションを指定できるvarargsパラメータが含まれています。これらのパラメータのタイプはLinker.Optionです。

1つのリンカー・オプションはLinker.Option.captureCallState(String...)です。このオプションは、ダウンコール・メソッド・ハンドルに関連付けられた外部関数を呼び出した直後に、実行状態の一部を保存するために使用します。これを使用して、特定のスレッド・ローカル変数を取得できます。"errno"文字列とともに使用すると、C標準ライブラリで定義されているerrno値が取得されます。errnoを設定するネイティブ関数のダウンコール・ハンドルを作成するときに、このリンカー・オプションを("errno"文字列とともに)指定します。

errnoを設定するC標準ライブラリ関数の例はlogで、その引数の自然(底e)対数を計算します。この値がゼロ未満の場合、errnoはドメイン・エラーを表す値33に設定されます。ほとんどのユーザーは33をドメイン・エラーとして認識しないため、C標準ライブラリ関数strerrorを起動すると、errno値のテキストによる説明が返されます。

次の例では、log関数を起動し、captureCallState("errno")を使用して、log関数によって設定されたエラー・メッセージを取得します。

    static double invokeLog(double v) throws Throwable {
        
        double result = Double.NaN;
        
        // Setup handles
        Linker.Option ccs = Linker.Option.captureCallState("errno");
        StructLayout capturedStateLayout = Linker.Option.captureStateLayout();
        VarHandle errnoHandle = capturedStateLayout.varHandle(PathElement.groupElement("errno"));

        // log C Standard Library function
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdLib = linker.defaultLookup();
        MethodHandle log = linker.downcallHandle(
            stdLib.find("log").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE),
            ccs);
        
        // strerror C Standard Library function
        MethodHandle strerror = linker.downcallHandle(
            stdLib.find("strerror").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT));

        // Actual invocation
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment capturedState = arena.allocate(capturedStateLayout);

            result = (double) log.invokeExact(capturedState, v);

            if (Double.isNaN(result)) {
                // Indicates that an error occurred per the documentation of
                // the 'log' command.
                
                // Get more information by consulting the value of errno:
                int errno = (int) errnoHandle.get(capturedState);
                System.out.println("errno: " + errno); // 33
                
                // Convert errno code to a string message:
                String errrorString = ((MemorySegment) strerror.invokeExact(errno))
                    .reinterpret(Long.MAX_VALUE).getUtf8String(0);
                System.out.println("errno string: " + errrorString); // Domain error
            }
        }
        return result;        
    }

この例では、メソッドcaptureStateLayout()errno関数の構造レイアウトを返します。詳細は、「メモリー・レイアウトおよび構造化アクセス」を参照してください。

次のようにinvokeLog(double)を呼び出すとします。

        System.out.println("log(2.718): " + invokeLog(2.718));
        System.out.println("log(-1): " + invokeLog(-1));

例では、次のような出力が表示されます:

log(2.718): 0.999896315728952
errno: 33
errno string: Domain error
log(-1): NaN

ヒント:

次のコードを使用して、オペレーティング・システムのLinker.Option.captureCallState(String...)オプションに対してサポートされている取得値レイアウトの名前を取得します:
List<String> capturedNames = Linker.Option.captureStateLayout()
        .memberLayouts()
        .stream() 
        .map(MemoryLayout::name)
        .flatMap(Optional::stream)
        .toList();