Compared to .NET's P/Invoke this is still way too convoluted. Of course Java has its own domain problem such as treating everything as a reference (and thus pointer, there is a reason Java has NullPointerException rather than NullReferenceException) and the lack of stack value types (everything lives on heap unless escape analysis allows some data to stay on stack, but it is uncontrollable anyway) makes translation of Plain-Old-Data (POD) types in Java very difficult, which is mostly a no-op with C#. That's why JNI exists as a mediator between native code and Java VM.
In C# I can just do something like this conceptual code:
Java's FFI is currently a very low-level. As the article points you, you don't actually have to do this: the jextract tool will generate the bindings for you from header files.
I'm sure someone will come along and write annotations to do exactly as you describe there. The Java language folks tend to be very conservative about putting stuff in the official API, cuz they know it'll have to stay there for 30+ years. They prefer to let the community write something like annotations over low-level APIs.
Can be even simpler now (you can declare it as a local function in a method, so this works when copied to Program.cs as is):
var text = "Hello, World!"u8;
write(1, text, text.Length);
[DllImport("libc")]
static extern nint write(nint fd, ReadOnlySpan<byte> buf, nint count);
(note: it's recommended to use [LibraryImport] instead for p/invoke declarations that require marshalling as it does not require JIT/runtime marshalling but just generates (better) p/invoke stub at build time)
In C# I can just do something like this conceptual code:
```
// FILE *fopen(const char *filename, const char *mode)
[DllImport("libc")] public unsafe extern nint fopen([MarshalAs(UnmanagedType.LPStr)] string filename, [MarshalAs(UnmanagedType.LPStr)] string mode);
// char *fgets(char *str, int n, FILE *stream)
[DllImport("libc")] public unsafe extern nint fgets([MarshalAs(UnmanagedType.LPStr)] string str, int n, nint stream);
// int fclose(FILE *stream)
[DllImport("libc")] public unsafe extern int fclose(nint stream);
```
So much less code, and so much more precise than any of the Java JNI and FFI stuff.