Next: , Previous: Tutorial-Memory, Up: Tutorial


4.9 Calling Lisp from C

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 is downloaded.

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 defcallback, and CFFI effectively creates a C function to be passed as a function pointer.

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 data-size nil))
               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 defcallback form.

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.1 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.

Using the CURLoption enumeration in curl.h once more, we can describe the new option by modifying and reevaluating define-curl-options.

  (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

Footnotes

[1] Unfortunately, we can't protect against all non-local exits, such as returns and throws, because unwind-protect cannot be used to “short-circuit” a non-local exit in Common Lisp, due to proposal minimal in 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.