Next: , Previous: , Up: Foreign Types   [Contents][Index]


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.


Next: , Previous: , Up: Foreign Types   [Contents][Index]