jextract
is a simple - but convenient - tool which generates a Java API from one or more native C headers. The tool can be obtained by building the foreign-jextract branch of Panama foreign repository.
Interacting with the jextract
tool usually involves two steps:
- Use the
jextract
tool to generate a java interface for some C header files - Write a Java program which invokes the wrapper API points generated by
jextract
The jextract
tool provides some basic options in order to control how the extraction process works; these are listed below:
-C <String>
- specify arguments to be passed to the underlying Clang parser-I <String>
- specify include files path-l <String>
- specify a library (name or full absolute path) which should be linked when the generated API is loaded-d <String>
- specify where to place generated files-t <String>
specify the target package for the generated classes--filter <String>
- simple string-based filtering mechanism; only symbols from headers whose absolute path contains the specified string will be included in the generated API--source
- generate java sources instead of classfiles
The remainder of this documents shows some basic usage examples of the jextract
tool.
#ifndef helloworld_h
#define helloworld_h
extern void helloworld(void);
#endif /* helloworld_h */
#include <stdio.h>
#include "helloworld.h"
void helloworld(void) {
printf("Hello World!\n");
}
cc -shared -o libhelloworld.dylib helloworld.c
jextract -t org.hello -lhelloworld helloworld.h
import static org.hello.helloworld_h.*;
public class HelloWorld {
public static void main(String[] args) {
helloworld();
}
}
java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign HelloWorld.java
jextract \
-l python2.7 \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/python2.7/ \
-t org.python \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/python2.7/Python.h
import static jdk.incubator.foreign.CLinker.*;
import static jdk.incubator.foreign.MemoryAddress.NULL;
// import jextracted python 'header' class
import static org.python.Python_h.*;
public class PythonMain {
public static void main(String[] args) {
String script = "print(sum([33, 55, 66])); print('Hello from Python!')\n";
Py_Initialize();
try (var str = toCString(script)) {
PyRun_SimpleStringFlags(str, NULL);
Py_Finalize();
}
}
}
java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign \
-Djava.library.path=/System/Library/Frameworks/Python.framework/Versions/2.7/lib \
PythonMain.java
jextract \
-l readline -t org.unix \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/readline/readline.h
import static org.unix.readline_h.*;
import static jdk.incubator.foreign.CLinker.*;
public class Readline {
public static void main(String[] args) {
try (var str = toCString("name? ")) {
// call "readline" API
var p = readline(str);
// print char* as is
System.out.println(p);
// convert char* ptr from readline as Java String & print it
System.out.println("Hello, " + toJavaStringRestricted(p));
}
}
}
java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign \
-Djava.library.path=/usr/local/opt/readline/lib/ Readline.java
jextract -t org.unix -lcurl \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/curl/ \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/curl/curl.h
import static jdk.incubator.foreign.MemoryAddress.NULL;
import static org.jextract.curl_h.*;
import static jdk.incubator.foreign.CLinker.*;
public class CurlMain {
public static void main(String[] args) {
var urlStr = args[0];
curl_global_init(CURL_GLOBAL_DEFAULT());
var curl = curl_easy_init();
if(!curl.equals(NULL)) {
try (var url = toCString(urlStr)) {
curl_easy_setopt(curl, CURLOPT_URL(), url.address());
int res = curl_easy_perform(curl);
if (res != CURLE_OK()) {
String error = toJavaStringRestricted(curl_easy_strerror(res));
System.out.println("Curl error: " + error);
curl_easy_cleanup(curl);
}
}
}
curl_global_cleanup();
}
}
# run this shell script by passing a URL as first argument
java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign \
-Djava.library.path=/usr/lib CurlMain.java $*
BLAS is a popular library that allows fast matrix and vector computation: http://www.netlib.org/blas/.
On Mac, blas is available as part of the OpenBLAS library: https://github.com/xianyi/OpenBLAS/wiki
OpenBLAS is an optimized BLAS library based on GotoBLAS2 1.13 BSD version.
You can install openblas using HomeBrew
brew install openblas
It installs include and lib directories under /usr/local/opt/openblas
The following command can be used to extract cblas.h on MacOs
jextract -C "-D FORCE_OPENBLAS_COMPLEX_STRUCT" \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
-l openblas -t blas /usr/local/opt/openblas/include/cblas.h
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.NativeScope;
import blas.*;
import static blas.cblas_h.*;
import static jdk.incubator.foreign.CLinker.*;
public class TestBlas {
public static void main(String[] args) {
int Layout;
int transa;
double alpha, beta;
int m, n, lda, incx, incy, i;
Layout = CblasColMajor();
transa = CblasNoTrans();
m = 4; /* Size of Column ( the number of rows ) */
n = 4; /* Size of Row ( the number of columns ) */
lda = 4; /* Leading dimension of 5 * 4 matrix is 5 */
incx = 1;
incy = 1;
alpha = 1;
beta = 0;
try (var scope = NativeScope.unboundedScope()) {
var a = scope.allocateArray(C_DOUBLE, new double[] {
1.0, 2.0, 3.0, 4.0,
1.0, 1.0, 1.0, 1.0,
3.0, 4.0, 5.0, 6.0,
5.0, 6.0, 7.0, 8.0
});
var x = scope.allocateArray(C_DOUBLE, new double[] {
1.0, 2.0, 1.0, 1.0
});
var y = scope.allocateArray(C_DOUBLE, n);
cblas_dgemv(Layout, transa, m, n, alpha, a, lda, x, incx, beta, y, incy);
/* Print y */
for (i = 0; i < n; i++) {
System.out.print(String.format(" y%d = %f\n", i, MemoryAccess.getDoubleAtIndex(y, i)));
}
}
}
}
jextract \
-C "-D FORCE_OPENBLAS_COMPLEX_STRUCT" \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
-l openblas -t blas /usr/local/opt/openblas/include/cblas.h
On Mac OS, lapack is installed under /usr/local/opt/lapack directory.
jextract \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
-l lapacke -t lapack \
--filter lapacke.h \
/usr/local/opt/lapack/include/lapacke.h
import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.NativeScope;
import lapack.*;
import static lapack.lapacke_h.*;
import static jdk.incubator.foreign.CLinker.*;
public class TestLapack {
public static void main(String[] args) {
/* Locals */
try (var scope = NativeScope.unboundedScope()) {
var A = scope.allocateArray(C_DOUBLE, new double[]{
1, 2, 3, 4, 5, 1, 3, 5, 2, 4, 1, 4, 2, 5, 3
});
var b = scope.allocateArray(C_DOUBLE, new double[]{
-10, 12, 14, 16, 18, -3, 14, 12, 16, 16
});
int info, m, n, lda, ldb, nrhs;
/* Initialization */
m = 5;
n = 3;
nrhs = 2;
lda = 5;
ldb = 5;
/* Print Entry Matrix */
print_matrix_colmajor("Entry Matrix A", m, n, A, lda );
/* Print Right Rand Side */
print_matrix_colmajor("Right Hand Side b", n, nrhs, b, ldb );
System.out.println();
/* Executable statements */
// printf( "LAPACKE_dgels (col-major, high-level) Example Program Results\n" );
/* Solve least squares problem*/
info = LAPACKE_dgels(LAPACK_COL_MAJOR(), (byte)'N', m, n, nrhs, A, lda, b, ldb);
/* Print Solution */
print_matrix_colmajor("Solution", n, nrhs, b, ldb );
System.out.println();
System.exit(info);
}
}
static void print_matrix_colmajor(String msg, int m, int n, MemorySegment mat, int ldm) {
int i, j;
System.out.printf("\n %s\n", msg);
for( i = 0; i < m; i++ ) {
for( j = 0; j < n; j++ ) System.out.printf(" %6.2f", MemoryAccess.getDoubleAtIndex(mat, i+j*ldm));
System.out.printf( "\n" );
}
}
}
java -Dforeign.restricted=permit \
--add-modules jdk.incubator.foreign \
-Djava.library.path=/usr/local/opt/lapack/lib \
TestLapack.java
jextract \
-t org.unix \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
--filter libproc.h \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/libproc.h
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.NativeScope;
import org.unix.*;
import static jdk.incubator.foreign.MemoryAddress.NULL;
import static org.unix.libproc_h.*;
public class LibprocMain {
private static final int NAME_BUF_MAX = 256;
public static void main(String[] args) {
try (var scope = NativeScope.unboundedScope()) {
// get the number of processes
int numPids = proc_listallpids(NULL, 0);
// allocate an array
var pids = scope.allocateArray(CLinker.C_INT, numPids);
// list all the pids into the native array
proc_listallpids(pids, numPids);
// convert native array to java array
int[] jpids = pids.toIntArray();
// buffer for process name
var nameBuf = scope.allocateArray(CLinker.C_CHAR, NAME_BUF_MAX);
for (int i = 0; i < jpids.length; i++) {
int pid = jpids[i];
// get the process name
proc_name(pid, nameBuf, NAME_BUF_MAX);
String procName = CLinker.toJavaString(nameBuf);
// print pid and process name
System.out.printf("%d %s\n", pid, procName);
}
}
}
}
java -Dforeign.restricted=permit \
--add-modules jdk.incubator.foreign \
-Djava.library.path=/usr/lib LibprocMain.java
- Download libgit2 v1.0.0 source from https://github.com/libgit2/libgit2/releases
- Use cmake to build from libgit2
- Let ${LIBGIT2_HOME} be the directory where you expanded libgit2 sources.
- Let ${LIBGIT2_HOME}/build be the build directory where libgit2.dylib is built.
jextract \
-t com.github -lgit2 \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ \
-I ${LIBGIT2_HOME}/include/ \
-I ${LIBGIT2_HOME}/include/git2 \
${LIBGIT2_HOME}/include/git2.h
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.NativeScope;
import static com.github.git2_h.*;
import static jdk.incubator.foreign.CLinker.*;
import static jdk.incubator.foreign.MemoryAddress.NULL;
public class GitClone {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("java GitClone <url> <path>");
System.exit(1);
}
git_libgit2_init();
try (var scope = NativeScope.unboundedScope()) {
var repo = scope.allocate(C_POINTER);
var url = toCString(args[0], scope);
var path = toCString(args[1], scope);
System.out.println(git_clone(repo, url, path, NULL));
}
git_libgit2_shutdown();
}
}
# file run.sh
java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign \
-Djava.library.path=${LIBGIT2_HOME}/build/ \
GitClone.java $*
sh run.sh https://github.com/libgit2/libgit2.git libgit2
jextract \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sqlite3.h \
-t org.sqlite -lsqlite3
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.NativeScope;
import static jdk.incubator.foreign.MemoryAddress.NULL;
import static org.sqlite.sqlite3_h.*;
import static jdk.incubator.foreign.CLinker.*;
public class SqliteMain {
public static void main(String[] args) throws Exception {
try (var scope = NativeScope.unboundedScope()) {
// char** errMsgPtrPtr;
var errMsgPtrPtr = scope.allocate(C_POINTER);
// sqlite3** dbPtrPtr;
var dbPtrPtr = scope.allocate(C_POINTER);
int rc = sqlite3_open(toCString("employee.db",scope), dbPtrPtr);
if (rc != 0) {
System.err.println("sqlite3_open failed: " + rc);
return;
} else {
System.out.println("employee db opened");
}
// sqlite3* dbPtr;
var dbPtr = MemoryAccess.getAddress(dbPtrPtr);
// create a new table
var sql = toCString(
"CREATE TABLE EMPLOYEE (" +
" ID INT PRIMARY KEY NOT NULL," +
" NAME TEXT NOT NULL," +
" SALARY REAL NOT NULL )", scope);
rc = sqlite3_exec(dbPtr, sql, NULL, NULL, errMsgPtrPtr);
if (rc != 0) {
System.err.println("sqlite3_exec failed: " + rc);
System.err.println("SQL error: " + toJavaStringRestricted(MemoryAccess.getAddress(errMsgPtrPtr)));
sqlite3_free(MemoryAccess.getAddress(errMsgPtrPtr));
} else {
System.out.println("employee table created");
}
// insert two rows
sql = toCString(
"INSERT INTO EMPLOYEE (ID,NAME,SALARY) " +
"VALUES (134, 'Xyz', 200000.0); " +
"INSERT INTO EMPLOYEE (ID,NAME,SALARY) " +
"VALUES (333, 'Abc', 100000.0);", scope
);
rc = sqlite3_exec(dbPtr, sql, NULL, NULL, errMsgPtrPtr);
if (rc != 0) {
System.err.println("sqlite3_exec failed: " + rc);
System.err.println("SQL error: " + toJavaStringRestricted(MemoryAccess.getAddress(errMsgPtrPtr)));
sqlite3_free(MemoryAccess.getAddress(errMsgPtrPtr));
} else {
System.out.println("rows inserted");
}
int[] rowNum = new int[1];
// callback to print rows from SELECT query
var callback = sqlite3_exec$callback.allocate((a, argc, argv, columnNames) -> {
System.out.println("Row num: " + rowNum[0]++);
System.out.println("numColumns = " + argc);
var argv_seg = argv.asSegmentRestricted(C_POINTER.byteSize() * argc);
var columnNames_seg = columnNames.asSegmentRestricted(C_POINTER.byteSize() * argc);
for (int i = 0; i < argc; i++) {
String name = toJavaStringRestricted(MemoryAccess.getAddressAtIndex(columnNames_seg, i));
String value = toJavaStringRestricted(MemoryAccess.getAddressAtIndex(argv_seg, i));
System.out.printf("%s = %s\n", name, value);
}
return 0;
}, scope);
// select query
sql = toCString("SELECT * FROM EMPLOYEE", scope);
rc = sqlite3_exec(dbPtr, sql, callback, NULL, errMsgPtrPtr);
if (rc != 0) {
System.err.println("sqlite3_exec failed: " + rc);
System.err.println("SQL error: " + toJavaStringRestricted(MemoryAccess.getAddress(errMsgPtrPtr)));
sqlite3_free(MemoryAccess.getAddress(errMsgPtrPtr));
} else {
System.out.println("done");
}
sqlite3_close(dbPtr);
}
}
}
java -Dforeign.restricted=permit \
--add-modules jdk.incubator.foreign \
-Djava.library.path=/usr/lib SqliteMain.java
jextract -t opengl -lGL -l/System/Library/Frameworks/GLUT.framework/Versions/Current/GLUT \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ \
-C-F/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/GLUT.framework/Headers/glut.h
import jdk.incubator.foreign.CLinker;
import static jdk.incubator.foreign.CLinker.*;
import jdk.incubator.foreign.NativeScope;
import static opengl.glut_h.*;
public class Teapot {
private float rot = 0;
Teapot(NativeScope scope) {
// Reset Background
glClearColor(0f, 0f, 0f, 0f);
// Setup Lighting
glShadeModel(GL_SMOOTH());
var pos = scope.allocateArray(C_FLOAT, new float[] {0.0f, 15.0f, -15.0f, 0});
glLightfv(GL_LIGHT0(), GL_POSITION(), pos);
var spec = scope.allocateArray(C_FLOAT, new float[] {1, 1, 1, 0});
glLightfv(GL_LIGHT0(), GL_AMBIENT(), spec);
glLightfv(GL_LIGHT0(), GL_DIFFUSE(), spec);
glLightfv(GL_LIGHT0(), GL_SPECULAR(), spec);
var shini = scope.allocate(C_FLOAT, 113);
glMaterialfv(GL_FRONT(), GL_SHININESS(), shini);
glEnable(GL_LIGHTING());
glEnable(GL_LIGHT0());
glEnable(GL_DEPTH_TEST());
}
void display() {
glClear(GL_COLOR_BUFFER_BIT() | GL_DEPTH_BUFFER_BIT());
glPushMatrix();
glRotatef(-20f, 1f, 1f, 0f);
glRotatef(rot, 0f, 1f, 0f);
glutSolidTeapot(0.5d);
glPopMatrix();
glutSwapBuffers();
}
void onIdle() {
rot += 0.1;
glutPostRedisplay();
}
public static void main(String[] args) {
try (var scope = NativeScope.unboundedScope()) {
var argc = scope.allocate(C_INT, 0);
glutInit(argc, argc);
glutInitDisplayMode(GLUT_DOUBLE() | GLUT_RGB() | GLUT_DEPTH());
glutInitWindowSize(500, 500);
glutCreateWindow(CLinker.toCString("Hello Panama!", scope));
var teapot = new Teapot(scope);
var displayStub = glutDisplayFunc$func.allocate(teapot::display, scope);
var idleStub = glutIdleFunc$func.allocate(teapot::onIdle, scope);
glutDisplayFunc(displayStub);
glutIdleFunc(idleStub);
glutMainLoop();
}
}
}
java -XstartOnFirstThread -Dforeign.restricted=permit --add-modules jdk.incubator.foreign \
-Djava.library.path=.:/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries/ Teapot.java $*
-
Download tensorflow library from
-
extract the downloaded tar in a directory called LIBTENSORFLOW_HOME
jextract --source \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ \
-t org.tensorflow \
-I ${LIBTENSORFLOW_HOME}/include \
-l ${LIBTENSORFLOW_HOME}/lib/libtensorflow.dylib \
${LIBTENSORFLOW_HOME}/include/tensorflow/c/c_api.h
javac --add-modules jdk.incubator.foreign org/tensorflow/*.java
The following Python program should be run to create and save model which will read and printed by a Java program.
Note: you need to install tensorflow package to run this python script.
import tensorflow as tf
from tensorflow.keras import models, layers
from tensorflow.keras.datasets import mnist
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128,activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(
loss='sparse_categorical_crossentropy',
optimizer=tf.keras.optimizers.Adam(0.001),
metrics=['accuracy'],
)
print(model.summary())
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images/255.0
test_images = test_images/255.0
model.fit(train_images, train_labels,
epochs=4, batch_size=128, verbose=1)
test_loss, test_accuracy = model.evaluate(test_images, test_labels)
print(test_loss, test_accuracy)
model.save("saved_mnist_model")
import jdk.incubator.foreign.*;
import static jdk.incubator.foreign.CLinker.*;
import static jdk.incubator.foreign.MemoryAccess.*;
import static jdk.incubator.foreign.MemoryAddress.*;
import static org.tensorflow.c_api_h.*;
// simple program that loads saved model and prints basic info on operations in it
public class TensorflowLoadSavedModel {
public static void main(String... args) throws Exception {
System.out.println("TensorFlow C library version: " + toJavaStringRestricted(TF_Version()));
if (args.length == 0) {
System.err.println("java TensorflowLoadSavedModel <saved model dir>");
System.exit(1);
}
try (var scope = NativeScope.unboundedScope()) {
var graph = TF_NewGraph();
var status = TF_NewStatus();
var sessionOpts = TF_NewSessionOptions();
var savedModelDir = toCString(args[0], scope);
var tags = scope.allocate(C_POINTER, toCString("serve", scope));
var session = TF_LoadSessionFromSavedModel(sessionOpts, NULL, savedModelDir, tags, 1, graph, NULL, status);
if (TF_GetCode(status) != TF_OK()) {
System.err.printf("cannot load session from saved model: %s\n",
toJavaStringRestricted(TF_Message(status)));
} else {
System.err.println("load session from saved model works!");
}
// print operations
var size = scope.allocate(C_LONGLONG);
var operation = NULL;
while (!(operation = TF_GraphNextOperation(graph, size)).equals(NULL)) {
System.out.printf("%s : %s\n",
toJavaStringRestricted(TF_OperationName(operation)),
toJavaStringRestricted(TF_OperationOpType(operation)));
}
TF_DeleteGraph(graph);
TF_DeleteSession(session, status);
TF_DeleteSessionOptions(sessionOpts);
TF_DeleteStatus(status);
}
}
}
java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign \
TensorflowLoadSavedModel.java saved_mnist_model
jextract -t org.unix \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/time.h
import static org.unix.time_h.*;
import static jdk.incubator.foreign.CLinker.*;
import jdk.incubator.foreign.*;
public class PanamaTime {
public static void main(String[] args) {
try (NativeScope scope = NativeScope.unboundedScope()) {
var now = scope.allocate(C_LONG, System.currentTimeMillis() / 1000);
MemorySegment time = tm.allocate(scope);
localtime_r(now, time);
System.err.printf("Time = %d:%d\n", tm.tm_hour$get(time), tm.tm_min$get(time));
}
}
}
java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign PanamaTime.java
# LIBCLANG_HOME is the directory where you've installed llvm 9.x or above
jextract --source -t org.llvm.clang -lclang \
-I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ \
-I ${LIBCLANG_HOME}/include/ \
-I ${LIBCLANG_HOME}/include/clang-c \
${LIBCLANG_HOME}/include/clang-c/Index.h
javac --add-modules jdk.incubator.foreign org/llvm/clang/*.java
import jdk.incubator.foreign.*;
import static jdk.incubator.foreign.CLinker.*;
import static jdk.incubator.foreign.MemoryAddress.NULL;
import static org.llvm.clang.Index_h.*;
public class ASTPrinter {
private static String asJavaString(MemorySegment clangStr) {
String str = toJavaStringRestricted(clang_getCString(clangStr));
clang_disposeString(clangStr);
return str;
}
public static void main(String[] args) {
if (args.length == 0) {
System.err.println("java ASTPrinter <C source or header>");
System.exit(1);
}
try (var scope = NativeScope.unboundedScope()) {
// parse the C header/source passed from the command line
var index = clang_createIndex(0, 0);
var tu = clang_parseTranslationUnit(index, toCString(args[0], scope),
NULL, 0, NULL, 0, CXTranslationUnit_None());
// array trick to update within lambda
var level = new int[1];
var visitor = new MemorySegment[1];
// clang Cursor visitor callback
visitor[0] = clang_visitChildren$visitor.allocate((cursor, parent, data) -> {
var kind = clang_getCursorKind(cursor);
var name = asJavaString(clang_getCursorSpelling(cursor));
var kindName = asJavaString(clang_getCursorKindSpelling(kind));
System.out.printf("%s %s %s", " ".repeat(level[0]), kindName, name);
var type = clang_getCursorType(cursor);
if (CXType.kind$get(type) != CXType_Invalid()) {
var typeName = asJavaString(clang_getTypeSpelling(type));
System.out.printf(" <%s>", typeName);
}
System.out.println();
// visit children
level[0]++;
clang_visitChildren(cursor, visitor[0], NULL);
level[0]--;
return CXChildVisit_Continue();
});
// get the AST root and visit it
var root = clang_getTranslationUnitCursor(tu);
clang_visitChildren(root, visitor[0], NULL);
clang_disposeTranslationUnit(tu);
clang_disposeIndex(index);
}
}
}
java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign \
ASTPrinter.java $*