Call functions in native shared libraries.
Load a library, bind C functions with explicit argument and return types, and manage native memory:
(require '[babashka.ffi :as ffi])
(ffi/load-system-library "sqlite3")
(def sqlite3-open (ffi/cfn "sqlite3_open" [:string :pointer] :int))
(with-open [arena (ffi/confined-arena)]
(let [pp (ffi/alloc arena :pointer)]
(sqlite3-open "x.db" pp)
(ffi/read pp :pointer)))
Every allocation belongs to an arena. The arena controls the lifetime of the memory.
Use these type keywords:
:void
:int :uint :long :ulong :int8 :uint8 :int16 :uint16 :int32
:uint32 :int64 :uint64 :size_t :ssize_t :char :byte
:bool :pointer :string :double :float
A pointer is a native java.lang.foreign.MemorySegment with a size. read and write check each access against this size. Pointers from C have size zero. Use reinterpret to specify their size before access.
:bool represents a one-byte C boolean and returns true or false. For predicates declared to return C int, use :int and test with zero?.
A layout describes memory: [:struct [[name type] ...]] for a struct and
[:array type n] for a fixed array. read returns a struct as a map and an
array as a vector. write accepts a map for a struct and a sequence for an
array. A field of a struct can be either, so char name[32] is
[:name [:array :char 32]].
[:union [[name type] ...]] describes a C union. read returns a union as a pointer to its bytes. Read the active member from that pointer using its type. write takes a [member value] pair. Unions cannot be passed by value.
Use place to select a layout member by name or by a path of names and array indices. Pass the result to read or write instead of a type. A place stores the member's offset and type.
read-array and write-array copy elements of one scalar type between native memory and a Java array of that width, as a memcpy.
Use a layout in a function signature to pass or return a struct by value. Represent struct values as maps:
(ffi/defcfn c-div "div" [:int :int] [:struct [[:quot :int] [:rem :int]]])
(c-div 7 2) ;=> {:quot 3 :rem 1}
On the JVM, struct calls use the FFM linker and need only the JDK. Native images use libffi for struct calls. See doc/guide.md.
Native images compile a fixed set of fast call shapes: up to six arguments, at most three mixed floating-point arguments or four of the same floating-point type, up to 10 integer or pointer arguments, and a :float return with up to four arguments. A fixed signature outside this set requires libffi. Binding fails if libffi is unavailable.
Native images use libffi for every variadic call. Without libffi, a variadic call throws. In a native image, callbacks support up to four arguments with at most two :double arguments, or up to six integer and pointer arguments. Callbacks do not support :float. The callback return type must be :void, an integer type, :pointer, or :double. Argument order does not affect these limits. See doc/guide.md for details and workarounds.
Add :& to argtypes to declare a variadic C function. The types before :& are the fixed parameters. Types after :& declare the tail once. With no types after :&, each call infers the tail types from its values. Integers and pointers use 64-bit integers. C promotion converts floats to doubles. Strings use C strings:
(ffi/defcfn c-open "open" [:string :int :&] :int)
(c-open path O_RDONLY) ; empty tail
(c-open path flags 0644) ; one-int tail, same binding
Call functions in native shared libraries.
Load a library, bind C functions with explicit argument and return types,
and manage native memory:
(require '[babashka.ffi :as ffi])
(ffi/load-system-library "sqlite3")
(def sqlite3-open (ffi/cfn "sqlite3_open" [:string :pointer] :int))
(with-open [arena (ffi/confined-arena)]
(let [pp (ffi/alloc arena :pointer)]
(sqlite3-open "x.db" pp)
(ffi/read pp :pointer)))
Every allocation belongs to an arena. The arena controls the lifetime of
the memory.
Use these type keywords:
:void
:int :uint :long :ulong :int8 :uint8 :int16 :uint16 :int32
:uint32 :int64 :uint64 :size_t :ssize_t :char :byte
:bool :pointer :string :double :float
A pointer is a native java.lang.foreign.MemorySegment with a size. read and
write check each access against this size. Pointers from C have size zero.
Use reinterpret to specify their size before access.
:bool represents a one-byte C boolean and returns true or false.
For predicates declared to return C int, use :int and test with zero?.
A layout describes memory: [:struct [[name type] ...]] for a struct and
[:array type n] for a fixed array. read returns a struct as a map and an
array as a vector. write accepts a map for a struct and a sequence for an
array. A field of a struct can be either, so `char name[32]` is
[:name [:array :char 32]].
[:union [[name type] ...]] describes a C union. read returns a union as a
pointer to its bytes. Read the active member from that pointer using its
type. write takes a [member value] pair. Unions cannot be passed by value.
Use place to select a layout member by name or by a path of names and array
indices. Pass the result to read or write instead of a type. A place stores
the member's offset and type.
read-array and write-array copy elements of one scalar type between
native memory and a Java array of that width, as a memcpy.
Use a layout in a function signature to pass or return a struct by value.
Represent struct values as maps:
(ffi/defcfn c-div "div" [:int :int] [:struct [[:quot :int] [:rem :int]]])
(c-div 7 2) ;=> {:quot 3 :rem 1}
On the JVM, struct calls use the FFM linker and need only the JDK. Native
images use libffi for struct calls. See doc/guide.md.
Native images compile a fixed set of fast call shapes: up to six
arguments, at most three mixed floating-point arguments or four of the
same floating-point type, up to 10 integer or pointer arguments, and a
:float return with up to four arguments. A fixed signature outside this
set requires libffi. Binding fails if libffi is unavailable.
Native images use libffi for every variadic call. Without libffi, a
variadic call throws. In a native image, callbacks support up to four
arguments with at most two :double arguments, or up to six
integer and pointer arguments. Callbacks do not support :float. The
callback return type must be :void, an integer type, :pointer, or :double.
Argument order does not affect these limits. See doc/guide.md for details
and workarounds.
Add :& to argtypes to declare a variadic C function. The types before :& are
the fixed parameters. Types after :& declare the tail once. With no types
after :&, each call infers the tail types from its values.
Integers and pointers use 64-bit integers. C promotion converts floats to
doubles. Strings use C strings:
(ffi/defcfn c-open "open" [:string :int :&] :int)
(c-open path O_RDONLY) ; empty tail
(c-open path flags 0644) ; one-int tail, same bindingCall functions in native shared libraries with node:ffi on Node.js.
Use the same names and argument order as the JVM namespace:
(require '[babashka.ffi :as ffi])
(ffi/load-system-library "sqlite3")
(def sqlite3-open (ffi/cfn "sqlite3_open" [:string :pointer] :int))
(ffi/with-open [arena (ffi/confined-arena)]
(let [pp (ffi/alloc arena :pointer)]
(sqlite3-open "x.db" pp)
(ffi/read pp :pointer)))
Needs Node.js 26.1 or newer. Runs under nbb, ClojureScript and shadow-cljs. A ClojureScript compile needs JDK 25 or newer, because the macros come from ffi.clj.
Use these type keywords:
:void
:int :uint :long :ulong :int8 :uint8 :int16 :uint16 :int32
:uint32 :int64 :uint64 :size_t :ssize_t :char :byte
:bool :pointer :string :double :float
A pointer is a Pointer: an address with a size and the arena that owns it. read and write check each access against this size. Pointers from C have size zero. reinterpret specifies their size before access.
A 64-bit integer returns as a number when it is a safe integer, otherwise as a bigint. Arguments accept either.
Use ffi/with-open to close an arena. It closes the arena when the body returns, so do not return a promise that still uses the arena.
Layouts, place, read-array, write-array, copy and clone work as on the JVM. read-array returns a typed array.
node:ffi does not support these, and cfn throws for each:
Call functions in native shared libraries with node:ffi on Node.js.
Use the same names and argument order as the JVM namespace:
(require '[babashka.ffi :as ffi])
(ffi/load-system-library "sqlite3")
(def sqlite3-open (ffi/cfn "sqlite3_open" [:string :pointer] :int))
(ffi/with-open [arena (ffi/confined-arena)]
(let [pp (ffi/alloc arena :pointer)]
(sqlite3-open "x.db" pp)
(ffi/read pp :pointer)))
Needs Node.js 26.1 or newer. Runs under nbb, ClojureScript and
shadow-cljs. A ClojureScript compile needs JDK 25 or newer, because the
macros come from ffi.clj.
Use these type keywords:
:void
:int :uint :long :ulong :int8 :uint8 :int16 :uint16 :int32
:uint32 :int64 :uint64 :size_t :ssize_t :char :byte
:bool :pointer :string :double :float
A pointer is a Pointer: an address with a size and the arena that owns it.
read and write check each access against this size. Pointers from C have
size zero. reinterpret specifies their size before access.
A 64-bit integer returns as a number when it is a safe integer, otherwise
as a bigint. Arguments accept either.
Use ffi/with-open to close an arena. It closes the arena when the body returns, so do not return
a promise that still uses the arena.
Layouts, place, read-array, write-array, copy and clone work as on the
JVM. read-array returns a typed array.
node:ffi does not support these, and cfn throws for each:
- a struct by value in a signature
- a variadic signature, :&
- a function pointer as the symbol. Bind a function by name.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |