Using the jextract tool

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:

  1. Use the jextract tool to generate a java interface for some C header files
  2. 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:

The remainder of this documents shows some basic usage examples of the jextract tool.

Hello World

Hello World C Header (helloworld.h)


#ifndef helloworld_h
#define helloworld_h

extern void helloworld(void);

#endif /* helloworld_h */

Hello World C Source (helloworld.c)


#include <stdio.h>

#include "helloworld.h"

void helloworld(void) {
    printf("Hello World!\n");
}

Building Hello World


cc -shared -o libhelloworld.dylib helloworld.c

jextract a Jar file for helloworld.h


jextract -t org.hello -lhelloworld helloworld.h

Java program that uses extracted helloworld interface


import static org.hello.helloworld_h.*;

public class HelloWorld {
    public static void main(String[] args) {
        helloworld();
    }
}

Running the Java code that invokes helloworld


java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign HelloWorld.java

Embedding Python interpreter in your Java program (Mac OS)

jextract Python.h


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

Java program that uses extracted Python interface


import jdk.incubator.foreign.ResourceScope;
import static jdk.incubator.foreign.CLinker.*;
import static jdk.incubator.foreign.MemoryAddress.NULL;
// import jextracted python 'header' class
import static org.python.Python_h.*;
import org.python.*;

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 scope = ResourceScope.newConfinedScope()) {
            var str = toCString(script, scope);
            PyRun_SimpleStringFlags(str, NULL);
            Py_Finalize();
        }
    }
}

Running the Java code that calls Python interpreter


java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
    -Djava.library.path=/System/Library/Frameworks/Python.framework/Versions/2.7/lib \
    PythonMain.java

Using readline library from Java code (Mac OS)

jextract readline.h


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

Java code that uses readline


import jdk.incubator.foreign.ResourceScope;
import static org.unix.readline_h.*;
import static jdk.incubator.foreign.CLinker.*;
import org.unix.*;

public class Readline {
    public static void main(String[] args) {
       try (var scope = ResourceScope.newConfinedScope()) {
            var url = toCString("name? ", scope);

            // call "readline" API
            var p = readline(url);

            // print char* as is
            System.out.println(p);
            // convert char* ptr from readline as Java String & print it
            System.out.println("Hello, " + toJavaString(p));

            freeMemory(p);
        }
    }
}

Running the java code that uses readline

java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
    -Djava.library.path=/usr/local/opt/readline/lib/ Readline.java

Using libcurl from Java (Mac OS)

jextract curl.h


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

Java code that uses libcurl


import jdk.incubator.foreign.ResourceScope;
import static jdk.incubator.foreign.MemoryAddress.NULL;
import static org.jextract.curl_h.*;
import static jdk.incubator.foreign.CLinker.*;
import org.jextract.*;

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 scope = ResourceScope.newConfinedScope()) {
               var url = toCString(urlStr, scope);
               curl_easy_setopt(curl, CURLOPT_URL(), url.address());
               int res = curl_easy_perform(curl);
               if (res != CURLE_OK()) {
                   String error = toJavaString(curl_easy_strerror(res));
                   System.out.println("Curl error: " + error);
                   curl_easy_cleanup(curl);
               }
           }
       }
       curl_global_cleanup();
   }
}

Running the java code that uses libcurl


# run this shell script by passing a URL as first argument
java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
    -Djava.library.path=/usr/lib CurlMain.java $*

Using BLAS library

BLAS is a popular library that allows fast matrix and vector computation: http://www.netlib.org/blas/.

Installing OpenBLAS (Mac OS)

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

jextracting cblas.h (MacOS)

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

Java sample code that uses cblas library


import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
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 = ResourceScope.newConfinedScope()) {
            var allocator = SegmentAllocator.ofScope(scope);
            var a = allocator.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 = allocator.allocateArray(C_DOUBLE, new double[] {
                1.0, 2.0, 1.0, 1.0
            });
            var y = allocator.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)));
            }
        }
    }
}

Compiling and running the above BLAS sample


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

Using LAPACK library (Mac OS)

On Mac OS, lapack is installed under /usr/local/opt/lapack directory.

jextracting lapacke.h


jextract \
   -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include \
   -l lapacke -t lapack \
   /usr/local/opt/lapack/include/lapacke.h

Java sample code that uses LAPACK library


import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
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 = ResourceScope.newConfinedScope()) {
            var allocator = SegmentAllocator.ofScope(scope);
            var A = allocator.allocateArray(C_DOUBLE, new double[]{
                    1, 2, 3, 4, 5, 1, 3, 5, 2, 4, 1, 4, 2, 5, 3
            });
            var b = allocator.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" );
        }
    }
}

Compiling and running the above LAPACK sample


java --enable-native-access=ALL-UNNAMED \
    --add-modules jdk.incubator.foreign \
    -Djava.library.path=/usr/local/opt/lapack/lib \
    TestLapack.java

Using libproc library to list processes from Java (Mac OS)

jextract libproc.h


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/libproc.h

Java program that uses libproc to list processes


import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
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 = ResourceScope.newConfinedScope()) {
            var allocator = SegmentAllocator.ofScope(scope);
            // get the number of processes
            int numPids = proc_listallpids(NULL, 0);
            // allocate an array
            var pids = allocator.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 = allocator.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);
            }
        }
    }
}

Compiling and running the libproc sample


java --enable-native-access=ALL-UNNAMED \
    --add-modules jdk.incubator.foreign \
    -Djava.library.path=/usr/lib LibprocMain.java

Using libgit2 from Java (Mac OS)

Getting and building libgit2

jextract git2.h


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

Java program that uses libgit2 to clone github repo


import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import static com.github.git2_h.*;
import static jdk.incubator.foreign.CLinker.*;
import static jdk.incubator.foreign.MemoryAddress.NULL;
import com.github.*;

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 = ResourceScope.newConfinedScope()) {
              var allocator = SegmentAllocator.ofScope(scope);
              var repo = allocator.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();
    }
}

Compiling and running the libgit2 sample


# file run.sh

java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
    -Djava.library.path=${LIBGIT2_HOME}/build/ \
    GitClone.java $*

Cloning a github repo using the above run.sh command


sh run.sh https://github.com/libgit2/libgit2.git libgit2

Using sqlite3 library from Java (Mac OS)

jextract sqlite3.h


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

Java program that uses sqlite3


import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import org.sqlite.*;
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 = ResourceScope.newConfinedScope()) {
            var allocator = SegmentAllocator.ofScope(scope);
            // char** errMsgPtrPtr;
            var errMsgPtrPtr = allocator.allocate(C_POINTER);

            // sqlite3** dbPtrPtr;
            var dbPtrPtr = allocator.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: " + toJavaString(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: " + toJavaString(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.asSegment(C_POINTER.byteSize() * argc, scope);
                var columnNames_seg = columnNames.asSegment(C_POINTER.byteSize() * argc, scope);
                for (int i = 0; i < argc; i++) {
                     String name = toJavaString(MemoryAccess.getAddressAtIndex(columnNames_seg, i));
                     String value = toJavaString(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: " + toJavaString(MemoryAccess.getAddress(errMsgPtrPtr)));
                sqlite3_free(MemoryAccess.getAddress(errMsgPtrPtr));
            } else {
                System.out.println("done");
            }

            sqlite3_close(dbPtr);
        }
    }
}

Compiling and running the sqlite3 sample


java --enable-native-access=ALL-UNNAMED \
   --add-modules jdk.incubator.foreign \
   -Djava.library.path=/usr/lib SqliteMain.java

Using OpenGL library from Java (Mac OS)

jextract glut.h


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

Java program that uses OpenGL


import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import opengl.*;
import static jdk.incubator.foreign.CLinker.*;
import static opengl.glut_h.*;

public class Teapot {
    private float rot = 0;

    Teapot(SegmentAllocator allocator) {
        // Reset Background
        glClearColor(0f, 0f, 0f, 0f);
        // Setup Lighting
        glShadeModel(GL_SMOOTH());
        var pos = allocator.allocateArray(C_FLOAT, new float[] {0.0f, 15.0f, -15.0f, 0});
        glLightfv(GL_LIGHT0(), GL_POSITION(), pos);
        var spec = allocator.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 = allocator.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 = ResourceScope.newConfinedScope()) {
            var allocator = SegmentAllocator.ofScope(scope);
            var argc = allocator.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(allocator);
            var displayStub = glutDisplayFunc$func.allocate(teapot::display, scope);
            var idleStub = glutIdleFunc$func.allocate(teapot::onIdle, scope);
            glutDisplayFunc(displayStub);
            glutIdleFunc(idleStub);
            glutMainLoop();
        }
    }
}

Compiling and running the OpenGL sample


java -XstartOnFirstThread --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
    -Djava.library.path=.:/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries/ Teapot.java $*

Using tensorflow (Mac OS)

getting libtensorflow

jextract c_api.h


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

Python program that creates and saves model

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")

Java program that uses Tensorflow C API


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.*;
import org.tensorflow.*;

// 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: " + toJavaString(TF_Version()));

        if (args.length == 0) {
            System.err.println("java TensorflowLoadSavedModel <saved model dir>");
            System.exit(1);
        }

        try (var scope = ResourceScope.newConfinedScope()) {
            var allocator = SegmentAllocator.ofScope(scope);
            var graph = TF_NewGraph();
            var status = TF_NewStatus();
            var sessionOpts = TF_NewSessionOptions();

            var savedModelDir = toCString(args[0], scope);
            var tags = allocator.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",
                    toJavaString(TF_Message(status)));
            } else {
                System.err.println("load session from saved model works!");
            }

            // print operations
            var size = allocator.allocate(C_LONG_LONG);
            var operation = NULL;
            while (!(operation = TF_GraphNextOperation(graph, size)).equals(NULL)) {
                System.out.printf("%s : %s\n",
                    toJavaString(TF_OperationName(operation)),
                    toJavaString(TF_OperationOpType(operation)));
            }

            TF_DeleteGraph(graph);
            TF_DeleteSession(session, status);
            TF_DeleteSessionOptions(sessionOpts);
            TF_DeleteStatus(status);
        }
    }
}

Compiling and running the Java Tensorflow sample


java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \
   TensorflowLoadSavedModel.java saved_mnist_model

Using time.h (Mac OS)

jextract time.h


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

Java program that uses POSIX time library


import static org.unix.time_h.*;
import static jdk.incubator.foreign.CLinker.*;
import jdk.incubator.foreign.*;
import org.unix.*;

public class PanamaTime {
    public static void main(String[] args) {
        try (var scope = ResourceScope.newConfinedScope()) {
            var allocator = SegmentAllocator.ofScope(scope);
            var now = allocator.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));
        }
    }
}

Compiling and running the time sample


java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign PanamaTime.java

Using libclang library (Mac OS)

jextract Index.h


# 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

Java program that uses libclang to print AST of a given C program


import jdk.incubator.foreign.*;
import static jdk.incubator.foreign.CLinker.*;
import static jdk.incubator.foreign.MemoryAddress.NULL;
import static org.llvm.clang.Index_h.*;
import org.llvm.clang.*;

public class ASTPrinter {
    private static String asJavaString(MemorySegment clangStr) {
        String str = toJavaString(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 = ResourceScope.newConfinedScope()) {
            // 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 MemoryAddress[1];

            // clang Cursor visitor callback
            visitor[0] = CXCursorVisitor.allocate((cursor, parent, data) -> {
                var kind = clang_getCursorKind(cursor);
                var name = asJavaString(clang_getCursorSpelling(scope, cursor));
                var kindName = asJavaString(clang_getCursorKindSpelling(scope, kind));
                System.out.printf("%s %s %s", " ".repeat(level[0]), kindName, name);
                var type = clang_getCursorType(scope, cursor);
                if (CXType.kind$get(type) != CXType_Invalid()) {
                    var typeName = asJavaString(clang_getTypeSpelling(scope, 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(scope, tu);
            clang_visitChildren(root, visitor[0], NULL);

            clang_disposeTranslationUnit(tu);
            clang_disposeIndex(index);
        }
    }
}

Compiling and running the libclang sample


java --enable-native-access=ALL-UNNAMED \
    -Djava.library.path=${LIBCLANG_HOME}/lib \
    --add-modules jdk.incubator.foreign \
    ASTPrinter.java $*