Next: Allocating Foreign Objects, Previous: Optimizing Type Translators, Up: Foreign Types [Contents][Index]
For more involved C types than simple aliases to built-in types, such
as you can make with defctype
, CFFI allows declaration of
structures and unions with defcstruct
and defcunion
.
For example, consider this fictional C structure declaration holding some personal information:
struct person { int number; char* reason; };
The equivalent defcstruct
form follows:
(defcstruct person
(number :int)
(reason :string))
By default, convert-from-foreign (and also mem-ref) will
make a plist with slot names as keys, and convert-to-foreign will
translate such a plist to a foreign structure. A user wishing to define
other translations should use the :class
argument to
defcstruct, and then define methods for
translate-from-foreign and
translate-into-foreign-memory that specialize on this class,
possibly calling call-next-method
to translate from and to the
plists rather than provide a direct interface to the foreign object.
The macro translation-forms-for-class
will generate the forms
necessary to translate a Lisp class into a foreign structure and vice
versa.
Please note that this interface is only for those that must know about
the values contained in a relevant struct. If the library you are
interfacing returns an opaque pointer that needs only be passed to
other C library functions, by all means just use :pointer
or a
type-safe definition munged together with defctype
and type
translation. To pass or return a structure by value to a function, load
the cffi-libffi system and specify the structure as (:struct
structure-name)
. To pass or return the pointer, you can use
either :pointer
or (:pointer (:struct
structure-name))
.
Just like how translate-from-foreign had
expand-from-foreign
to optimize away the generic function call
and translate-to-foreign had the same in
expand-to-foreign
, translate-into-foreign-memory has
expand-into-foreign-memory
.
Let’s use our person
struct in an example. However, we are
going to spice it up by using a lisp struct rather than a plist to
represent the person in lisp.
First we redefine person
very slightly.
(defcstruct (person :class c-person)
(number :int)
(reason :string))
By adding :class
we can specialize the translate-*
methods on the type c-person
.
Next we define a lisp struct to use instead of the plists.
(defstruct lisp-person
(number 0 :type integer)
(reason "" :type string))
And now let’s define the type translators we know already:
(defmethod translate-from-foreign (ptr (type c-person)) (with-foreign-slots ((number reason) ptr (:struct person)) (make-lisp-person :number number :reason reason))) (defmethod expand-from-foreign (ptr (type c-person)) `(with-foreign-slots ((number reason) ,ptr (:struct person)) (make-lisp-person :number number :reason reason))) (defmethod translate-into-foreign-memory (value (type c-person) ptr) (with-foreign-slots ((number reason) ptr (:struct person)) (setf number (lisp-person-number value) reason (lisp-person-reason value))))
At this point everything works, we can convert to and from our
lisp-person
and foreign person
. If we macroexpand
(setf (mem-aref ptr '(:struct person)) x)
we get something like:
(let ((#:store879 x))
(translate-into-foreign-memory #:store879 #<c-person person>
(inc-pointer ptr 0))
#:store879)
Which is good, but now we can do better and get rid of that generic
function call to translate-into-foreign-memory
.
(defmethod expand-into-foreign-memory (value (type c-person) ptr)
`(with-foreign-slots ((number reason) ,ptr (:struct person))
(setf number (lisp-person-number ,value)
reason (lisp-person-reason ,value))))
Now we can expand again so see the changes:
;; this: (setf (mem-aref ptr '(:struct person)) x) ;; expands to this ;; (simplified, downcased, etc..) (let ((#:store887 x)) (with-foreign-slots ((number reason) (inc-pointer ptr 0) (:struct person)) (setf number (lisp-person-number #:store887) reason (lisp-person-reason #:store887))) #:store887)
And there we are, no generic function overhead.
Previous versions of CFFI accepted the
“bare” structure-name as a type specification, which was
interpreted as a pointer to the structure. This is deprecated and
produces a style warning. Using this deprecated form means that
mem-aref retains its prior meaning and returns a pointer. Using
the (:struct structure-name)
form for the type,
mem-aref provides a Lisp object translated from the
structure (by default a plist). Thus the semantics are consistent with all
types in returning the object as represented in Lisp, and not a pointer,
with the exception of the “bare” structure compatibility retained.
In order to obtain the pointer, you should use the function mem-aptr.
See defcstruct for more details.
Next: Allocating Foreign Objects, Previous: Optimizing Type Translators, Up: Foreign Types [Contents][Index]