我正在使用 JDK 19 中的外部函数和内存 API (JEP 424) 将基于 JNA 的库移植到“纯”Java。
我已经成功实现了
sysctl()
函数并使用以下方式获取值:
public static MethodHandle sysctl = Linker.nativeLinker().downcallHandle(
SYSTEM_LIBRARY.lookup("sysctl").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS,
ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));
在实现此功能之前的调试过程中,我偶尔会得到 -1 返回值:
成功完成后,返回值0;否则返回值-1,并设置全局变量 errno 来指示错误。
在 JNA 中,可以使用
Native.getLastError()
轻松获得。 这个答案展示了我如何在 JNI 中编写代码来获取它,但 JEP 424 的重点是消除对 JNA 或 JNI 的需要。
Windows API 有一个有用的 GetLastError 函数可以映射,如 JDK 中的测试
所示但是,我无法在 macOS 上找到任何可以映射以返回的本机函数
errno
。我发现的最接近的是 perror()
它将错误打印到 STDERR:
public static MethodHandle perror = Linker.nativeLinker()
.downcallHandle(SYSTEM_LIBRARY.lookup("perror").orElseThrow(),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS));
虽然输出到 STDERR 在短期内很有帮助,但我想使用该值进行异常处理/错误消息。
这似乎是一个足够常见的用例,JDK 中应该有一个方法,但我找不到它。
除了暂时将 STDERR 重定向到我可以解析的字符串之外,还有什么想法吗?
我无法使用Mac来测试这个,但如果这个来源是准确的:https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/sys/errno.h。 auto.html
那么
errno
是一个宏,定义如下:
__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS
这是对函数的调用,该函数返回指向
int*
值的 errno
。
因此,要从 Java 访问它应该可行:
static final MethodHandle __error = Linker.nativeLinker()
.downcallHandle(SYSTEM_LIBRARY.lookup("__error").orElseThrow(),
FunctionDescriptor.of(ValueLayout.ADDRESS));
static int errno() {
try {
MemoryAddress addr = (MemoryAddress) __error.invokeExact();
return addr.get(ValueLayout.JAVA_INT, 0);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
请注意,目前(JDK 19)这并不是 100% 可靠的访问
errno
的方式,因为虚拟机可能会阻塞某处并在读取值之前用自己的系统调用覆盖 errno
。
我们一直在讨论如何更可靠地访问
errno
以及虚拟机可以覆盖的其他线程局部变量。同时,这也应该使访问该值变得更加容易。
对于 JDK 21+ 有一个推荐的替代方案,在 Linker 类的文档中提到:
try (var confinedArena = Arena.ofConfined()) {
// SYSTEM_LIBRARY doesn't exist anymore
Function<String, MemorySegment> lookup = symbol ->
Linker.nativeLinker().defaultLookup().find(symbol)
.or(() -> SymbolLookup.loaderLookup().find(symbol)).orElseThrow();
// declare the errno as state to be captured, directly after the downcall without any interence of the
// JVM runtime
StructLayout capturedStateLayout = Linker.Option.captureStateLayout();
VarHandle errnoHandle = capturedStateLayout.varHandle(MemoryLayout.PathElement.groupElement("errno"));
Linker.Option ccs = Linker.Option.captureCallState("errno");
MethodHandle sysctl = Linker.nativeLinker().downcallHandle(
lookup.apply("sysctl"), FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS,
ValueLayout.ADDRESS, ValueLayout.JAVA_LONG), ccs);
MemorySegment capturedState = confinedArena.allocate(capturedStateLayout);
try {
int result = (int) sysctl.invoke(capturedState, ...);
int errno = (int) errnoHandle.get(capturedState);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
要获取错误字符串,可以调用 strerror 函数:
String errnoString(int errno){
AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
MethodHandle strerror = Linker.nativeLinker()
.downcallHandle(lookup("strerror"),
FunctionDescriptor.of(POINTER, ValueLayout.JAVA_INT));
try {
MemorySegment str = (MemorySegment) strerror.invokeExact(errno);
System.out.println(str);
return str.getUtf8String(0);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
啊,你找的函数叫
strerror
,它可以将错误代码(errno
)映射到字符串。
基于 Apple 文档。错误消息在
errno
变量中设置:
当系统调用检测到错误时,它返回一个整数值来指示 ing 失败(通常为 -1)并相应地设置变量 errno。
成功的调用永远不会设置 errno;一旦设置,它仍然存在 直到另一个错误发生。仅应在出现错误后进行检查。
#include <string.h> //header for strerror
#include <stdio.h>
#include <sys/errno.h> //Header for error enumerations.
int main(int argc, const char * argv[]) {
//taking "EPERM" as an errno example
int eperm = EPERM;
char * errStr = strerror(eperm);
printf("%s\n", errStr);//prints 'Operation not permitted'
return 0;
}
所以尝试
strerror
将 errno
转换为错误消息字符串