Checking for Native Errors Using errno
Some C standard library functions indicate errors by setting the value of the C standard library macro errno. You can access this value with a FFM API linker option.
The Linker::downcallHandle method contains a varargs parameter
that enables you to specify additional linker options. These parameters are of type
Linker.Option
.
One linker option is Linker.Option.captureCallState(String...), which you use to save portions of the execution state immediately after calling a foreign function associated with a downcall method handle. You can use it to capture certain thread-local variables. When used with the "errno" string, it captures the errno value as defined by the C standard library. Specify this linker option (with the "errno" string) when creating a downcall handle for a native function that sets errno.
An example of a C standard library function that sets errno is log, which computes the natural (base e) logarithm of its argument. If this value is less than zero, then errno is set to the value 33, which represents a domain error. As most users won't recognize 33 as a domain error, you can invoke the C standard library function strerror, which returns a textual description of the errno value.
The following example invokes the log function then uses
captureCallState("errno")
to obtain error messages set by the
log function:
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;
}
In this example, the method captureStateLayout() returns a structure
layout of the errno
function. See Memory Layouts and Structured Access for more information.
Suppose that you call invokeLog(double)
as follows:
System.out.println("log(2.718): " + invokeLog(2.718));
System.out.println("log(-1): " + invokeLog(-1));
The example then prints output similar to the following:
log(2.718): 0.999896315728952
errno: 33
errno string: Domain error
log(-1): NaN
Tip:
Use the following code to obtain the names of the supported captured value layouts for the Linker.Option.captureCallState(String...) option for your operating system:List<String> capturedNames = Linker.Option.captureStateLayout()
.memberLayouts()
.stream()
.map(MemoryLayout::name)
.flatMap(Optional::stream)
.toList();