The libcurl
tutorial says we'll want to set many options before
performing any download actions. This is done through
curl_easy_setopt
:
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-regexp
s, 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 curl-code
type defined earlier.
defcfun
just puts a convenient façade on
foreign-funcall
.1 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.
[1] This isn't entirely true; some Lisps
don't support foreign-funcall
, so defcfun
is implemented
without it. defcfun
may also perform optimizations that
foreign-funcall
cannot.