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

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

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

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

errnoを設定するC標準ライブラリ関数の例は、fopen(const char *filename, const char *mode)で、この関数は指定されたモードを使用してファイルを開きます。モードの例には、読取り用のファイルを開くrと、書込み用のファイルを開くwがあります。fopenが存在しないファイルを開こうとすると、errnoは値2に設定されます。これは、ファイルが存在しないことを意味します。ほとんどのユーザーはこれを知らないため、C標準ライブラリ関数strerrorを呼び出すと、errno値のテキストによる説明が返されます。

次の例では、次のC標準ライブラリ関数を使用してファイルを開き、読み取ります:

  • FILE *fopen(const char *filename, const char *mode): 前述のように、ファイルfilenameをモードmode(この例ではモードはr)で開き、MemorySegmentで表されるFILEオブジェクトへのポインタを返します。
  • char *strerror(int errnum): 前述のように、errnumの値に対応するエラー・メッセージ文字列へのポインタを返します。
  • char *fgets(char *str, int n, FILE *stream): FILEオブジェクトstreamへのポインタからn-1文字を読み取り、strが指す配列に格納します。
  • int feof(FILE *stream): ファイル・ポインタstreamでファイルの終わりインジケータが検出された場合、ゼロ以外の値を返します。それ以外の場合は、0(ゼロ)を戻します。
  • int fclose(FILE *stream): streamが指すファイルを閉じます。

この例では、存在する必要があるファイルReadFileWithFopen.javaおよび存在しないファイルfile-doesnot-exist.txtを読み取ります。この例では、fopen関数を呼び出すと、captureCallState("errno")を使用して、それによって設定されたエラー・メッセージを取得します:

import java.lang.foreign.*;
import java.lang.foreign.MemoryLayout.*;
import java.lang.invoke.*;

public class ReadFileWithFopen {
    
    static int BUFFER_SIZE = 1024;
    
    static void readFile(String path) throws Throwable {
        
        System.out.println("Reading " + path);
        
        // Setup handles
        Linker.Option ccs = Linker.Option.captureCallState("errno");
        StructLayout capturedStateLayout = Linker.Option.captureStateLayout();
        VarHandle errnoHandle = capturedStateLayout.varHandle(PathElement.groupElement("errno"));
        
        // Linker and symbol lookup for C Standard Library functions
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdLib = linker.defaultLookup();
        
        // char *strerror(int errnum)
        MethodHandle strerror = linker.downcallHandle(
			stdLib.find("strerror").orElseThrow(),
			FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT));     
        
        // FILE *fopen(const char *filename, const char *mode)
        MethodHandle fopen = linker.downcallHandle(stdLib.find("fopen").orElseThrow(),
            FunctionDescriptor.of(
                ValueLayout.ADDRESS,
                ValueLayout.ADDRESS,
                ValueLayout.ADDRESS),
			ccs);
            
        // char *fgets(char *str, int n, FILE *stream)
        MethodHandle fgets = linker.downcallHandle(stdLib.find("fgets").orElseThrow(),
            FunctionDescriptor.of(
                ValueLayout.ADDRESS,
                ValueLayout.ADDRESS,
                ValueLayout.JAVA_INT,
                ValueLayout.ADDRESS));
                
        // int feof(FILE *stream)
        MethodHandle feof = linker.downcallHandle(stdLib.find("feof").orElseThrow(),
            FunctionDescriptor.of(
                ValueLayout.JAVA_INT,
                ValueLayout.ADDRESS));
    
        // int fclose(FILE *stream)
        MethodHandle fclose = linker.downcallHandle(stdLib.find("fclose").orElseThrow(),
            FunctionDescriptor.of(
                ValueLayout.JAVA_INT,
                ValueLayout.ADDRESS));
            
        // Actual invocation
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment capturedState = arena.allocate(capturedStateLayout);
            MemorySegment location = arena.allocateFrom(path);
            MemorySegment openMode =  arena.allocateFrom("r");

            var filePointer = (MemorySegment) fopen.invokeExact(capturedState, location, openMode);

            if (filePointer.address() == 0) {
                printErrnoCode(errnoHandle, capturedState, strerror);
                return;
            }
            
            var buffer = arena.allocate(ValueLayout.JAVA_BYTE, BUFFER_SIZE);
            var eof = (int) feof.invokeExact(filePointer);
            
            while (eof == 0) {
                System.out.print(buffer.getString(0));
                var read = (MemorySegment) fgets.invokeExact(buffer, BUFFER_SIZE, filePointer);
                eof = (int) feof.invokeExact(filePointer);
            }
            
            var close = (int) fclose.invokeExact(filePointer);
            
        }
    }
    
    private static void printErrnoCode(
        VarHandle errnoHandle,
        MemorySegment capturedState,
        MethodHandle strerror) throws Throwable {
        
        // Get more information by consulting the value of errno:
        int errno = (int) errnoHandle.get(capturedState, 0);
                
        // An errno value of 2 (ENOENT) is "No such file or directory"
        System.out.println("errno: " + errno);

        // Convert errno code to a string message:
        String errrorString = ((MemorySegment) strerror.invokeExact(errno))
            .reinterpret(Long.MAX_VALUE).getString(0);
        System.out.println("errno string: " + errrorString);
    }
    
    public static void main(String[] args) {
        try {
            readFile("ReadFileWithFopen.java");
            readFile("file-does-not-exist.txt");
        } catch (Throwable t) {
            System.out.println(t.getMessage());
        }
    }
}

この例の出力は次のとおりです:

Reading ReadFileWithFopen.java
import java.lang.foreign.*;
import java.lang.foreign.MemoryLayout.*;
import java.lang.invoke.*;
...
Reading file-does-not-exist.txt
errno: 2
errno string: No such file or directory

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

ヒント:

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