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