Skip to content

JNA

Using tech.jna, we can link C native libraries into Clojure.

(ns jna
  (:require [tech.jna :as jna]
            [tech.jna.base :as base])
  (:import [java.nio FloatBuffer]
           [com.sun.jna Native Pointer NativeLibrary]))

(jna/def-jna-fn "c" memset
  "Set byte memory to a value"
  com.sun.jna.Pointer
  [data identity]
  [value int]
  [n-byes jna/size-t])

(def test-ary (float-array [1 2 3 4]))
(vec test-ary)
(memset test-ary 0 (* 4 Float/BYTES))
(vec test-ary)

(FloatBuffer/wrap test-ary)

(def test-ary-buf *1)
test-ary-buf
(memset test-ary-buf 1 (* 4 (Float/BYTES)))
(vec test-ary)

(defn float-ptr->vec [ptr n-floats]
  (->> (range n-floats)
       (mapv #(.getFloat ptr (* % Float/BYTES)))))

(def test-ptr (-> (Native/malloc (* 4 Float/BYTES))
                  (Pointer.)))

test-ptr

(float-ptr->vec test-ptr 4)

(memset test-ptr 0 (* 4 Float/BYTES))
(float-ptr->vec test-ptr 4)

The following function is useful for interactive programming to reload native libraries.

(defn reload!
  "Reload a shared library."
  [libname]
  (when-let [native-lib (get @base/*loaded-libraries* libname)]
    (.dispose native-lib)
    (swap! base/*loaded-libraries* dissoc libname)
    (base/do-load-library libname)))

tech.v3.jna

In the latest version, one has to dispose of the process manually.

(import '[com.sun.jna NativeLibrary])
(defn reload!
  "Reload a shared library."
  [libname]
  (defn reload! [libname]
    (try
      (.dispose (NativeLibrary/getInstance libname))
      (catch Exception e))
    (NativeLibrary/getInstance libname))

Full example for passing C struct

#include <stdio.h>
#include <stdlib.h>

typedef struct point {
  int x;
  int y;
} point_t;

void greeting() {
  printf("Hello, greeting!");
};

int greet() {
  printf("Hello, World!");
  return 0;
}

// simple test with simple types
int add(int x, int y) {
  return x+y;
}

// compose to simple type
int projection_x(point_t *a) {
  return a->x;
};

// composite to simple type
int sum_all(point_t *a, point_t *b) {
  return a->x + b->x + a->y + b->y;
};

// Notice that we passe pointers to the function
void plus(point_t *a, point_t *b, point_t *c) {
  c->x = a->x + b->x;
  c->y = a->y + b->y;
};

// Example of returning a point, see that we are still returning a pointer;
point_t *minus(point_t *a, point_t *b) {
  point_t* c = (point_t*) malloc(sizeof(point_t));
  c->x = a->x - b->x;
  c->y = a->y - b->y;
  return c;
};

Let's compile this

gcc -shared -o libs/libhello.so -fPIC cpp/hello.c
  • shared is to tell the artefacts will be used by other process or programs.

  • -fPIC means position independent code, used for library binary code.

    (ns hello
      (:require
       [tech.v3.datatype.ffi :as dt-ffi]
       [tech.v3.datatype.struct :as dt-struct])
      (:import [com.sun.jna NativeLibrary]))
    
    (println (. System getProperty "java.library.path"))
    
    (defn reload! [libname]
      (try
        (.dispose (NativeLibrary/getInstance libname))
        (catch Exception e))
      (NativeLibrary/getInstance libname))
    
    (defonce point
      (dt-struct/define-datatype!
        :point_t [{:name :x :datatype :int32}
                  {:name :y :datatype :int32}]))
    
    ;; Allocation to this type won't work as it is not defined in our library
    ;; (defonce person
    ;;   (dt-struct/define-datatype!
    ;;     :person_t [{:name :age :datatype :int32}
    ;;                {:name :weight :datatype :float32}]))
    
    (def fn-defs
      {:greeting     {:rettype :void}
       :greet        {:rettype :int32}
       :add          {:rettype  :int32
                      :argtypes [['x :int32] ['y :int32]]}
       :sum_all      {:rettype  :int32
                      :argtypes '[[a :pointer]
                                  [b :pointer]]}
       :projection_x {:rettype  :int32
                      :argtypes '[[a :pointer]]}
    
       :plus         {:rettype  :void
                      :argtypes '[[a :pointer]
                                  [b :pointer]
                                  [c :pointer]]}
       :minus        {:rettype  :pointer
                      :argtypes '[[a :pointer]
                                  [b :pointer]]}})
    
    (def library-def (dt-ffi/define-library fn-defs))
    
    ;; the name of the library is the last argument
    (def library-instance (dt-ffi/instantiate-library library-def "hello"))
    (defonce lib (dt-ffi/library-singleton #'fn-defs))
    
    (dt-ffi/library-singleton-set! lib nil)
    (dt-ffi/define-library-functions fn-defs
      (fn [fn-name] (dt-ffi/library-singleton-find-fn lib fn-name)) nil)
    
    (comment
      (reload! "hello") ;; use this if you want to change the library and experiment
      (add 1 100) ;; => 101
      (greet) ;; => 0, but should not print in the repl
      (greeting) ;; does nothing , but message will be shown when shutting down the repl
    
      ;; doing the complicated stuff now
      (let [a (dt-struct/new-struct :point_t {:container-type :native-heap})
            b (dt-struct/new-struct :point_t {:container-type :native-heap})
            c (dt-struct/new-struct :point_t {:container-type :native-heap})
            ;; filling the structures
            _ (do (.put a :x 2)
                  (.put a :y 3)
                  (.put b :x 5)
                  (.put b :y 5))]
        (println a)
        (println b)
        (plus a b c) ;; clearly not functional!
    
        {:max-norm (max_norm a b)
         :plus c
         :minus
         ;; transform a pointer back into a structure
         (dt-ffi/ptr->struct :point_t (minus a))}))
    

C style

Pointers

See also (generated)