Liking cljdoc? Tell your friends :D

babashka.ffi

clj

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 binding
cljs

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.
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.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close