We could use foreign-funcall
directly every time we wanted to
call curl_easy_setopt
. However, we can encapsulate some of the
necessary information with the following.
;;; We will use this type later in a more creative way. For ;;; now, just consider it a marker that this isn't just any ;;; pointer. (defctype easy-handle :pointer) (defmacro curl-easy-setopt (easy-handle enumerated-name value-type new-value) "Call `curl_easy_setopt' on EASY-HANDLE, using ENUMERATED-NAME as the OPTION. VALUE-TYPE is the CFFI foreign type of the third argument, and NEW-VALUE is the Lisp data to be translated to the third argument. VALUE-TYPE is not evaluated." `(foreign-funcall "curl_easy_setopt" easy-handle ,easy-handle curl-option ,enumerated-name ,value-type ,new-value curl-code))
Now we define a function for each kind of argument that encodes the
correct value-type
in the above. This can be done reasonably
in the define-curl-options
macroexpansion; after all, that is
where the different options are listed!
We could make cl:defun
forms in the expansion that simply call
curl-easy-setopt
; however, it is probably easier and clearer to
use defcfun
. define-curl-options
was becoming unwieldy,
so I defined some helpers in this new definition.
(defun curry-curl-option-setter (function-name option-keyword) "Wrap the function named by FUNCTION-NAME with a version that curries the second argument as OPTION-KEYWORD. This function is intended for use in DEFINE-CURL-OPTION-SETTER." (setf (symbol-function function-name) (let ((c-function (symbol-function function-name))) (lambda (easy-handle new-value) (funcall c-function easy-handle option-keyword new-value))))) (defmacro define-curl-option-setter (name option-type option-value foreign-type) "Define (with DEFCFUN) a function NAME that calls curl_easy_setopt. OPTION-TYPE and OPTION-VALUE are the CFFI foreign type and value to be passed as the second argument to easy_setopt, and FOREIGN-TYPE is the CFFI foreign type to be used for the resultant function's third argument. This macro is intended for use in DEFINE-CURL-OPTIONS." `(progn (defcfun ("curl_easy_setopt" ,name) curl-code (easy-handle easy-handle) (option ,option-type) (new-value ,foreign-type)) (curry-curl-option-setter ',name ',option-value))) (defmacro define-curl-options (type-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. Also, define functions for each option named set-`TYPE-NAME'-`OPTION-NAME', where OPTION-NAME is the NAME from the above destructuring." (flet ((enumerated-value (type offset) (+ (getf type-offsets type) offset)) ;; map PROCEDURE, destructuring each of ENUM-ARGS (map-enum-args (procedure) (mapcar (lambda (arg) (apply procedure arg)) enum-args)) ;; build a name like SET-CURL-OPTION-NOSIGNAL (make-setter-name (option-name) (intern (concatenate 'string "SET-" (symbol-name type-name) "-" (symbol-name option-name))))) `(progn (defcenum ,type-name ,@(map-enum-args (lambda (name type number) (list name (enumerated-value type number))))) ,@(map-enum-args (lambda (name type number) (declare (ignore number)) `(define-curl-option-setter ,(make-setter-name name) ,type-name ,name ,(ecase type (long :long) (objectpoint :pointer) (functionpoint :pointer) (off-t :long))))) ',type-name)))
Macroexpanding our define-curl-options
form once more, we
see something different:
(progn
(defcenum curl-option
(:noprogress 43)
(:nosignal 99)
(:errorbuffer 10010)
(:url 10002))
(define-curl-option-setter set-curl-option-noprogress
curl-option :noprogress :long)
(define-curl-option-setter set-curl-option-nosignal
curl-option :nosignal :long)
(define-curl-option-setter set-curl-option-errorbuffer
curl-option :errorbuffer :pointer)
(define-curl-option-setter set-curl-option-url
curl-option :url :pointer)
'curl-option)
Macroexpanding one of the new define-curl-option-setter
forms yields the following:
(progn
(defcfun ("curl_easy_setopt" set-curl-option-nosignal) curl-code
(easy-handle easy-handle)
(option curl-option)
(new-value :long))
(curry-curl-option-setter 'set-curl-option-nosignal ':nosignal))
Finally, let's try this out:
cffi-user> (set-curl-option-nosignal *easy-handle* 1)
=> 0
Looks like it works just as well. This interface is now reasonably
high-level to wash out some of the ugliness of the thinnest possible
curl_easy_setopt
FFI, without obscuring the remaining
C bookkeeping details we will explore.