Next: , Previous: Tutorial-Initializing, Up: Tutorial


4.5 Setting download options

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


Footnotes

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