6.19.7 Foreign Structs

Finally, one last note on foreign values before moving on to actually calling foreign functions. Sometimes you need to deal with C structs, which requires interpreting each element of the struct according to the its type, offset, and alignment. The (system foreign) module has some primitives to support this.

(use-modules (system foreign))
Scheme Procedure: sizeof type
C Function: scm_sizeof (type)

Return the size of type, in bytes.

type should be a valid C type, like int. Alternately type may be the symbol *, in which case the size of a pointer is returned. type may also be a list of types, in which case the size of a struct with ABI-conventional packing is returned.

Scheme Procedure: alignof type
C Function: scm_alignof (type)

Return the alignment of type, in bytes.

type should be a valid C type, like int. Alternately type may be the symbol *, in which case the alignment of a pointer is returned. type may also be a list of types, in which case the alignment of a struct with ABI-conventional packing is returned.

Guile also provides some convenience syntax to efficiently read and write C structs to and from bytevectors.

Scheme Syntax: read-c-struct bv offset
((field type) …) k

Read a C struct with fields of type type... from the bytevector bv, at offset offset. Bind the fields to the identifiers field..., and return (k field ...).

Unless cross-compiling, the field types are evaluated at macro-expansion time. This allows the resulting bytevector accessors and size/alignment computations to be completely inlined.

Scheme Syntax: write-c-struct bv offset
((field type) …)

Write a C struct with fields field... of type type... to the bytevector bv, at offset offset. Return zero values.

Like write-c-struct above, unless cross-compiling, the field types are evaluated at macro-expansion time.

For example, to define a parser and serializer for the equivalent of a struct { int64_t a; uint8_t b; }, one might do this:

(use-modules (system foreign) (rnrs bytevectors))

(define-syntax-rule
    (define-serialization (reader writer) (field type) ...)
  (begin
    (define (reader bv offset)
      (read-c-struct bv offset ((field type) ...) values))
    (define (writer bv offset field ...)
      (write-c-struct bv offset ((field type) ...)))))

(define-serialization (read-struct write-struct)
  (a int64) (b uint8))

(define bv (make-bytevector (sizeof (list int64 uint8))))

(write-struct bv 0 300 43)
(call-with-values (lambda () (read-struct bv 0))
  list)
⇒ (300 43)

There is also an older interface that is mostly equivalent to read-c-struct and write-c-struct, but which uses run-time dispatch, and operates on foreign pointers instead of bytevectors.

Scheme Procedure: parse-c-struct foreign types

Parse a foreign pointer to a C struct, returning a list of values.

types should be a list of C types.

Our parser and serializer example for struct { int64_t a; uint8_t b; } looks more like this:

(parse-c-struct (make-c-struct (list int64 uint8)
                               (list 300 43))
                (list int64 uint8))
⇒ (300 43)

As yet, Guile only has convenience routines to support conventionally-packed structs. But given the bytevector->pointer and pointer->bytevector routines, one can create and parse tightly packed structs and unions by hand. See the code for (system foreign) for details.