As DuncG suggested, you can use java.lang.foreign to access C functions. However, there are some caveats:
- As of Java 20, this is still a preview feature, which means public classes and their members may change, or may be removed entirely, in the next few Java releases.
- Compiling requires the compiler options
--enable-preview --release 20
. I also recommend the -Xlint:-preview
option.
- Running requires the Java command line options
--enable-preview --enable-native-access=com.example
. Replace com.example
with your module name, or replace it with ALL-UNNAMED
if you are not declaring a module.
import java.nio.ByteBuffer;
import java.io.IOException;
import java.lang.foreign.Linker;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.foreign.Arena;
import java.lang.invoke.MethodHandle;
import java.util.Objects;
import java.util.function.Consumer;
public class SHM {
private static final boolean IS_64_BIT =
System.getProperty("os.arch").contains("64");
private static MemorySegment lookupFunction(String name) {
return Linker.nativeLinker().defaultLookup().find(name).orElseThrow(
() -> new RuntimeException("Function " + name + "() not found."));
}
private static MemorySegment lookupValue(String name) {
return Linker.nativeLinker().defaultLookup().find(name).orElseThrow(
() -> new RuntimeException("Value " + name + " not found."));
}
private static final MethodHandle strerror =
Linker.nativeLinker().downcallHandle(
lookupFunction("strerror"),
FunctionDescriptor.of(
ValueLayout.ADDRESS, // return
ValueLayout.JAVA_INT) // errnum
);
private static final MethodHandle shmget =
Linker.nativeLinker().downcallHandle(
lookupFunction("shmget"),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // return
ValueLayout.JAVA_INT, // key
IS_64_BIT // size
? ValueLayout.JAVA_LONG
: ValueLayout.JAVA_INT,
ValueLayout.JAVA_INT) // shmflg
);
private static final MethodHandle shmat =
Linker.nativeLinker().downcallHandle(
lookupFunction("shmat"),
FunctionDescriptor.of(
ValueLayout.ADDRESS, // return
ValueLayout.JAVA_INT, // shmid
ValueLayout.JAVA_INT) // shmflg
);
private static final MethodHandle shmdt =
Linker.nativeLinker().downcallHandle(
lookupFunction("shmdt"),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // return
ValueLayout.ADDRESS) // shmaddr
);
private static String strerror(int errnum)
throws IOException {
MemorySegment message;
try {
message = (MemorySegment) strerror.invokeExact(errnum);
} catch (Throwable t) {
throw new IOException(t);
}
String messageStr;
try (Arena arena = Arena.openConfined()) {
// java.lang.foreign always returns memory addresses
// as zero-length MemorySegments.
MemorySegment bounded = MemorySegment.ofAddress(message.address(),
4096, arena.scope());
messageStr = bounded.getUtf8String(0);
}
return messageStr;
}
private static int errno()
throws IOException {
MemorySegment seg = lookupValue("errno");
int num;
try (Arena arena = Arena.openConfined()) {
// java.lang.foreign always returns memory addresses
// as zero-length MemorySegments.
MemorySegment bounded = MemorySegment.ofAddress(seg.address(),
Integer.BYTES, arena.scope());
num = bounded.get(ValueLayout.JAVA_INT, 0);
}
return num;
}
public void useSharedMemory(int shmKey,
int shmSize,
Consumer<ByteBuffer> task)
throws IOException {
if (shmSize < 0) {
throw new IllegalArgumentException("Size cannot negative.");
}
Objects.requireNonNull(task, "Task cannot be null.");
try (Arena arena = Arena.openConfined()) {
int shmid;
try {
if (IS_64_BIT) {
shmid = (int) shmget.invokeExact(shmKey, (long) shmSize, 0);
} else {
shmid = (int) shmget.invokeExact(shmKey, shmSize, 0);
}
} catch (Throwable t) {
throw new IOException(t);
}
if (shmid == -1) {
throw new IOException(
"shmget() returned " + shmid + ": " + strerror(errno()));
}
MemorySegment addr;
try {
addr = (MemorySegment) shmat.invokeExact(
shmid, MemorySegment.NULL, 0);
} catch (Throwable t) {
throw new IOException(t);
}
if (addr.address() == -1) {
throw new IOException(
"shmat() returned " + addr.address() + ": "
+ strerror(errno()));
}
// java.lang.foreign always returns memory addresses
// as zero-length MemorySegments.
MemorySegment boundedAddr =
MemorySegment.ofAddress(addr.address(), shmSize, arena.scope());
task.accept(boundedAddr.asByteBuffer());
int status;
try {
status = (int) shmdt.invokeExact(addr);
} catch (Throwable t) {
throw new IOException(t);
}
if (status != 0) {
throw new IOException(
"shmdt() returned " + status + ": " + strerror(errno()));
}
}
}
}
Fair warning: I have only done minimal testing on the above code.