If you have been reading
curl_easy_setopt(3), you should have noticed that some options
accept a function pointer. In particular, we need one function
pointer to set as
CURLOPT_WRITEFUNCTION, to be called by
libcurl rather than the reverse, in order to receive data as it
A binding writer without the aid of FFI usually approaches this problem by writing a C function that accepts C data, converts to the language’s internal objects, and calls the callback provided by the user, again in a reverse of usual practices.
The CFFI approach to callbacks precisely mirrors its differences
with the non-FFI approach on the “calling C from Lisp”
side, which we have dealt with exclusively up to now. That is, you
define a callback function in Lisp using
CFFI effectively creates a C function to be passed as a function
Implementor’s note: This is much trickier than calling C functions from Lisp, as it literally involves somehow generating a new C function that is as good as any created by the compiler. Therefore, not all Lisps support them. See Implementation Support, for information about CFFI support issues in this and other areas. You may want to consider changing to a Lisp that supports callbacks in order to continue with this tutorial.
Defining a callback is very similar to defining a callout; the main
difference is that we must provide some Lisp forms to be evaluated as
part of the callback. Here is the signature for the function the
:writefunction option takes:
size_t function(void *ptr, size_t size, size_t nmemb, void *stream);
Implementor’s note: size_t is almost always an unsigned int. You can get this and many other types using feature tests for your system by using cffi-grovel.
The above signature trivially translates into a CFFI
defcallback form, as follows.
;;; Alias in case size_t changes. (defctype size :unsigned-int) ;;; To be set as the CURLOPT_WRITEFUNCTION of every easy handle. (defcallback easy-write size ((ptr :pointer) (size size) (nmemb size) (stream :pointer)) (let ((data-size (* size nmemb))) (handler-case ;; We use the dynamically-bound *easy-write-procedure* to ;; call a closure with useful lexical context. (progn (funcall (symbol-value '*easy-write-procedure*) (foreign-string-to-lisp ptr :count data-size)) data-size) ;indicates success ;; The WRITEFUNCTION should return something other than the ;; #bytes available to signal an error. (error () (if (zerop data-size) 1 0)))))
First, note the correlation of the first few forms, used to declare the C function’s signature, with the signature in C syntax. We provide a Lisp name for the function, its return type, and a name and type for each argument.
In the body, we call the dynamically-bound
*easy-write-procedure* with a “finished” translation, of
pulling together the raw data and size into a Lisp string, rather than
deal with the data directly. As part of calling
curl_easy_perform later, we’ll bind that variable to a closure
with more useful lexical bindings than the top-level
Finally, we make a halfhearted effort to prevent non-local exits from
unwinding the C stack, covering the most likely case with an
error handler, which is usually triggered
unexpectedly.9 The reason is that most C code is written to
understand its own idiosyncratic error condition, implemented above in
the case of
curl_easy_perform, and more “undefined behavior”
can result if we just wipe C stack frames without allowing them to
execute whatever cleanup actions as they like.
CURLoption enumeration in curl.h once more, we
can describe the new option by modifying and reevaluating
(define-curl-options curl-option (long 0 objectpoint 10000 functionpoint 20000 off-t 30000) (:noprogress long 43) (:nosignal long 99) (:errorbuffer objectpoint 10) (:url objectpoint 2) (:writefunction functionpoint 11)) ;new item here
Finally, we can use the defined callback and the new
set-curl-option-writefunction to finish configuring the easy
handle, using the
callback macro to retrieve a CFFI
:pointer, which works like a function pointer in C code.
CFFI-USER> (set-curl-option-writefunction *easy-handle* (callback easy-write)) ⇒ 0
Unfortunately, we can’t protect against
all non-local exits, such as
unwind-protect cannot be used to “short-circuit” a
non-local exit in Common Lisp, due to proposal
ANSI issue EXIT-EXTENT. Furthermore, binding an
error handler prevents higher-up code from invoking restarts
that may be provided under the callback’s dynamic context. Such is
the way of compromise.