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();