libcurl tutorial says we’ll want to set many options before
performing any download actions. This is done through
CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);
We’ve introduced a new twist: variable arguments. There is no obvious
translation to the
defcfun form, particularly as there are four
possible argument types. Because of the way C works, we could define
four wrappers around
curl_easy_setopt, one for each type; in
this case, however, we’ll use the general-purpose macro
foreign-funcall to call this function.
To make things easier on ourselves, we’ll create an enumeration of the
kinds of options we want to set. The
enum CURLoption isn’t the
most straightforward, but reading the
CINIT C macro definition
should be enlightening.
(defmacro define-curl-options (name type-offsets &rest enum-args) "As with CFFI:DEFCENUM, except each of ENUM-ARGS is as follows: (NAME TYPE NUMBER) Where the arguments are as they are with the CINIT macro defined in curl.h, except NAME is a keyword. TYPE-OFFSETS is a plist of TYPEs to their integer offsets, as defined by the CURLOPTTYPE_LONG et al constants in curl.h." (flet ((enumerated-value (type offset) (+ (getf type-offsets type) offset))) `(progn (defcenum ,name ,@(loop for (name type number) in enum-args collect (list name (enumerated-value type number)))) ',name))) ;for REPL users' sanity (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))
With some well-placed Emacs
query-replace-regexps, you could
probably similarly define the entire
CURLoption enumeration. I
have selected to transcribe a few that we will use in this tutorial.
If you’re having trouble following the macrology, just macroexpand the
curl-option definition, or see the following macroexpansion,
conveniently downcased and reformatted:
(progn (defcenum curl-option (:noprogress 43) (:nosignal 99) (:errorbuffer 10010) (:url 10002)) 'curl-option)
That seems more than reasonable. You may notice that we only use the type to compute the real enumeration offset; we will also need the type information later.
First, however, let’s make sure a simple call to the foreign function works:
CFFI-USER> (foreign-funcall "curl_easy_setopt" :pointer *easy-handle* curl-option :nosignal :long 1 curl-code) ⇒ 0
foreign-funcall, despite its surface simplicity, can be used to
call any C function. Its first argument is a string, naming the
function to be called. Next, for each argument, we pass the name of
the C type, which is the same as in
defcfun, followed by a Lisp
object representing the data to be passed as the argument. The final
argument is the return type, for which we use the
type defined earlier.
defcfun just puts a convenient façade on
foreign-funcall.4 Our earlier call to
curl-global-init could have been written as follows:
CFFI-USER> (foreign-funcall "curl_global_init" :long 0 curl-code) ⇒ 0
Before we continue, we will take a look at what CFFI can and can’t do, and why this is so.
This isn’t entirely true; some Lisps
defcfun is implemented
defcfun may also perform optimizations that