Foreign function interface¶
EGCL-FFI provides foreign pointers, explicit foreign storage, shared libraries,
scalar calls, and callbacks on supported targets. These interfaces are callable
from Lisp; the old extension inventory's “not installed” label is obsolete.
They are EGCL interfaces, not drop-in substitutes for every CFFI or SB-ALIEN
operation.
Runtime and ABI requirements¶
Dynamic library loading requires a dynamic EGCL build with egcl-rt/c-ffi.
The Fedora native RPM and Android APK runtime provide dynamic builds. The
static Android CLI and default static musl CLI do not dynamically load libraries.
Foreign call and callback support are architecture-specific; ordinary Lisp
execution on a target does not establish FFI parity.
The caller supplies the exact C signature. The runtime cannot infer a prototype from a symbol address. A wrong return type, argument type, calling convention, or lifetime can corrupt a process despite valid Lisp syntax.
Foreign types¶
Common scalar designators include :int, :uint, :char, :uchar, :short,
:ushort, :long, :ulong, :int64, :uint64, :float, :double, and
:pointer. :void is used for a return with no value.
Do not assume C long or a pointer is the same size on every target. Query the
runtime's size and alignment, and use fixed-width types when the C interface
specifies a fixed width.
Size and alignment¶
Functions
(egcl-ffi:foreign-type-size type) ; size in bytes
(egcl-ffi:foreign-type-alignment type) ; alignment in bytes
These report the runtime's layout for a supported foreign type. Do not use a Lisp array's upgraded element type as proof that its storage has the same layout.
Pointers and storage¶
Pointer operations¶
Functions
(egcl-ffi:pointerp object)
(egcl-ffi:make-pointer address)
(egcl-ffi:pointer-address pointer)
(egcl-ffi:pointer-eq pointer-a pointer-b)
(egcl-ffi:null-pointer)
(egcl-ffi:null-pointer-p pointer)
(egcl-ffi:inc-pointer pointer byte-offset)
A foreign pointer is an opaque object, not an integer address or a pointer to a
moving Lisp value. make-pointer wraps an address; it does not allocate storage
or establish ownership. inc-pointer changes the address by bytes, not elements.
Allocation and release¶
Functions (egcl-ffi:foreign-alloc bytes) → pointer;
(egcl-ffi:foreign-free pointer)
Foreign storage has an explicit lifetime. Release the owning allocation once, after C has stopped retaining or using it. Do not free an interior alias. Freeing tracked storage invalidates its tracked aliases; double release and access through a freed tracked alias are errors. A raw address supplied from outside the allocator does not carry the same ownership information.
Reading and writing¶
Functions
(egcl-ffi:mem-ref pointer type &optional (offset 0))
(egcl-ffi:mem-set value pointer type &optional (offset 0))
(setf (egcl-ffi:mem-ref pointer type offset) value)
Offsets are bytes. The allocation must be large enough for the offset plus the size of the value. Tracked allocations receive bounds and lifetime checks; a foreign pointer is not permission to access arbitrary memory safely.
(let ((p (egcl-ffi:foreign-alloc 8)))
(unwind-protect
(progn
(setf (egcl-ffi:mem-ref p :int32) 21)
(setf (egcl-ffi:mem-ref p :int32 4) 2)
(* (egcl-ffi:mem-ref p :int32)
(egcl-ffi:mem-ref p :int32 4)))
(egcl-ffi:foreign-free p)))
;; => 42
Vector access¶
Macro (egcl-ffi:with-pointer-to-vector-data (pointer vector &optional type) body...)
Uses temporary foreign storage, copies the vector in, executes the body, copies
values back, and frees the storage. The default type is :unsigned-char.
The pointer is valid only inside the body. This is a copying interface, not a
promise to pin the Lisp vector in place. Cleanup also runs on nonlocal exit.
Libraries and calls¶
Library lifetime¶
Functions
(egcl-ffi:load-foreign-library path) ; library object
(egcl-ffi:foreign-library-p object) ; generalized boolean
(egcl-ffi:foreign-symbol-pointer name &optional library)
(egcl-ffi:close-foreign-library library)
Supply a pathname understood by the platform's dynamic loader. A library object is distinct from a foreign pointer. Supplying the library to symbol lookup makes the lookup scope explicit; omitting it uses the default lookup scope. Close the library only when no code can call its symbols or use its data. Failures are reported through the FFI error interface.
egcl-ffi:foreign-call¶
Function
Calls a foreign entry point. argument-types and arguments are corresponding
lists: arguments are not supplied as Lisp rest arguments. For a variadic C
function, supply the number of fixed arguments as the final parameter so the
runtime can apply the appropriate ABI rules and promotions.
For example, with a library exporting int twice(int):
(let ((library (egcl-ffi:load-foreign-library "./libexample.so")))
(unwind-protect
(egcl-ffi:foreign-call
(egcl-ffi:foreign-symbol-pointer "twice" library)
:int '(:int) '(21))
(egcl-ffi:close-foreign-library library)))
;; => 42
Build that example library on Linux with:
The C compiler and library must target the same architecture as the EGCL runtime doing the call. The sample filename and command are Linux-specific.
Callbacks¶
Callback lifetime¶
Functions
(egcl-ffi:make-callback function return-type argument-types)
(egcl-ffi:callback-pointer callback)
(egcl-ffi:foreign-callback-p object)
(egcl-ffi:free-callback callback)
(egcl-ffi:callback-error callback)
make-callback returns a callback object; use callback-pointer to obtain the
entry pointer passed to C. Keep the callback alive until all C references and
invocations have ended. Freeing it while C may still call it is invalid.
Callback errors cannot unwind arbitrarily through C. The bridge records the
failure and returns a zero result to C; the enclosing foreign call can then
signal an FFI error. callback-error consumes saved diagnostic text, including
errors recorded on a foreign thread. Callback support must be checked for the
target architecture separately from scalar foreign calls.
On an x86-64 runtime with callback support, a callback can be exercised through the same call interface before giving it to a C library:
(let ((callback (egcl-ffi:make-callback (lambda (x) (+ x 1)) :int '(:int))))
(unwind-protect
(egcl-ffi:foreign-call (egcl-ffi:callback-pointer callback)
:int '(:int) '(41))
(egcl-ffi:free-callback callback)))
;; => 42
Conditions and sandboxing¶
egcl-ffi:ffi-error is the public FFI condition. Invalid pointer ownership,
unsupported signatures, loader failures, and callback bridge errors can reach
this interface. Sandbox mode denies foreign access; using funcall instead of
a direct call does not bypass that policy.
Implementation reference: Public FFI wrappers.
Java integration¶
The Java integration chapter covers the in-process HotSpot JVM,
the primary JAVA API, descriptor-based EGCL-JVM calls, Java interfaces
implemented by Lisp callbacks, and reference ownership. Java calls use the
checked JVM bridge rather than application-supplied JNI prototypes.