Next: , Previous: Optimizing Type Translators, Up: Foreign Types


6.6 Foreign Structure Types

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

Optimizing translate-into-foreign-memory

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.

Compatibility note

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.