We could use
foreign-funcall directly every time we wanted to
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
value-type in the above. This can be done reasonably
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
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)))
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
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.