Files
javademos/jextract/README.md
T
asmhatreandClaude Sonnet 5.5 ecceaa8b32 Add jextract module: SQLite bindings generated with jextract, FFM arenas, C strings, out-parameters, errors, upcalls, and a JMH comparison with JNI
jextract-25 early-access build (25-jextract+2-4) on JDK 25.0.5+8 against SQLite 3.45.1: 25 functions and 13 constants
generated into 4 committed files (the whole header would be 83 files), a small Db wrapper, the four arena kinds
used wrongly on purpose, ADDRESS vs C_POINTER zero-length vs unbounded segments and the SIGSEGV from getString
on a NULL pointer, sqlite3_open out-parameter, return codes to exceptions, a sqlite3_exec upcall and the VM exit
when it throws, the JDK 25 native-access warning and --illegal-native-access=deny, library loading failures,
and a JMH benchmark of FFM vs hand-written JNI vs plain Java (indicative only: shared 2-CPU machine).

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X
2026-10-30 16:36:09 +00:00

55 lines
3.0 KiB
Markdown

# jextract: calling SQLite from Java with the FFM API
Companion code for the ankurm.com post **"Calling C from Java with jextract and the FFM API: A Real Library, No JNI"**. No `docs/` folder: the explanation lives in the article.
| Thing | Version |
|---|---|
| JDK | Temurin 25.0.5+8 (FFM API final since JDK 22, JEP 454) |
| jextract | `openjdk-25-jextract+2-4` early-access build, 2025-11-25 (jdk.java.net/jextract), libclang 13.0.0 |
| SQLite | 3.45.1 (Ubuntu `libsqlite3-dev 3.45.1-1ubuntu2.8`) |
| JMH | 1.37 |
| gcc | 13.3.0 (only for the JNI comparison) |
## Quickstart
```bash
sudo apt-get install libsqlite3-dev gcc # header + libsqlite3.so; gcc only for the JNI benchmark
export JAVA_HOME=/path/to/jdk-25
./scripts/fetch-jextract.sh # downloads and sha256-checks the jextract build into ./tools
export JEXTRACT=$PWD/tools/jextract-25/bin/jextract
./scripts/gen.sh # regenerate generated/ (the committed bindings)
./scripts/run-all.sh # compile everything, regenerate output/01..09 and 11
./scripts/fetch-jmh.sh && ./scripts/run-jmh.sh # the FFM vs JNI vs plain Java benchmark, about 4 minutes -> output/10-jmh.txt
```
Run any demo by hand: `java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Tour`.
## Layout
| Path | What |
|---|---|
| `scripts/gen.sh` | the jextract command, filtered to 25 functions and 13 constants |
| `generated/demo/sqlite/` | what jextract produced from that command (4 files, committed so you can read them) |
| `src/demo/` | hand-written code: `Db` (the wrapper), `Hello`, `Tour`, `Arenas`, `Errors`, `Trap`, `NullText`, `Callbacks` |
| `src/extra/V.java` | one-line program used by `run-all.sh` to test library loading with throwaway bindings |
| `jni/` | the JNI twin used by the benchmark: `SqliteJni.java` and `sqlite_jni.c` |
| `bench/src/bench/SqliteBench.java` | JMH benchmark: plain Java vs FFM vs JNI |
## Captured output (`output/`)
| File | What it is |
|---|---|
| `01-tools.txt` | versions of every tool used |
| `02-generate.txt` | the trimmed generation vs the whole header: file counts, line counts |
| `03-hello.txt` | first call, plus `-Djextract.trace.downcalls=true` |
| `04-native-access.txt` | JDK 25 with no `--enable-native-access` flag: the warning, and `--illegal-native-access=deny` |
| `05-tour.txt` | open, create, bind, insert, query |
| `06-arenas.txt` | confined, shared, auto and global arenas used wrongly |
| `07-pointer-traps.txt` | zero-length pointers, and the `SIGSEGV` from `getString` on a NULL pointer |
| `08-errors.txt` | return codes turned into exceptions |
| `09-callbacks.txt` | `sqlite3_exec` with an upcall, and an exception thrown inside it |
| `10-jmh.txt` | JMH numbers (machine dependent: shape, not result) |
| `11-library-loading.txt` | what `-l` produces, and the failure when the library name is wrong |
Timings and addresses change between runs; everything else is byte-stable. The Linux/x64 jextract build is used throughout; nothing here was run on macOS or Windows.