4 Getting Started
dxtr edited this page 2026-10-18 15:16:26 +02:00

Getting Started

Getting the code

cl-ffi is not in Quicklisp. With stift, require it in your project's stiftfile:

stift get https://fakehacker.cc/dxtr/cl-ffi.git

A project that uses a library built on cl-ffi, such as cl-vk, gets cl-ffi from that library's own stiftfile, as an indirect entry. Without stift, clone the repository where ASDF finds it, such as ~/common-lisp/.

cl-ffi needs SBCL on a 64-bit platform, and nothing else: no C compiler, no headers, no other Lisp library.

Loading

(asdf:load-system :cl-ffi)

(defpackage #:example
  (:use #:cl)
  (:local-nicknames (#:ffi #:cl-ffi)))
(in-package #:example)

The examples in this wiki write ffi: for cl-ffi.

A first binding

A library is defined once, by the files to look for on each platform, and loaded:

(ffi:define-foreign-library libz
  (:windows "zlib1.dll")
  (:unix (:or "libz.so.1" "libz.so")))

(ffi:use-foreign-library libz)

A C function becomes a Lisp function, typed:

(ffi:defcfun ("zlibVersion" zlib-version) :string
  "Return the version of zlib that was loaded.")

(zlib-version)                          ; => "1.3.2"

defcfun declaims the function's ftype from the C types, so SBCL checks its arguments, at compile time where it can. A :string result comes back as a Lisp string.

Memory for an out-parameter or a struct comes from the stack, for the extent of a body:

(ffi:defcfun ("compressBound" compress-bound) :ulong (length :ulong))
(ffi:defcfun ("compress" compress) :int
  (destination :pointer) (destination-length :pointer)
  (source :pointer) (source-length :ulong))

(let ((text (map '(vector (unsigned-byte 8)) #'char-code "hello, hello, hello")))
  (ffi:with-foreign-object (length :ulong)
    (setf (ffi:mem-ref length :ulong) (compress-bound (length text)))
    (ffi:with-foreign-pointer (output 128)
      (ffi:with-pointer-to-vector-data (input text)
        (compress output length input (length text)))
      (ffi:mem-ref length :ulong))))    ; => the compressed length

C calls Lisp through a callback:

(ffi:defcallback compare :int ((a :pointer) (b :pointer))
  (- (ffi:mem-ref a :int32) (ffi:mem-ref b :int32)))

(ffi:with-foreign-object (numbers :int32 4)
  (loop for n in '(3 1 4 1)
        for i from 0
        do (setf (ffi:mem-aref numbers :int32 i) n))
  (ffi:foreign-funcall "qsort" :pointer numbers :size 4 :size 4
                               :pointer (ffi:callback compare) :void)
  (loop for i below 4 collect (ffi:mem-aref numbers :int32 i)))   ; => (1 1 3 4)

Next: Types, or For cffi Users if you know cffi.