On this page:
make-cstruct-type
_  list-struct
define-cstruct
compute-offsets

3.7 C Struct Types🔗ℹ

procedure

(make-cstruct-type types    
  [abi    
  alignment    
  malloc-mode])  ctype?
  types : (non-empty-listof ctype?)
  abi : (or/c #f 'default 'stdcall 'sysv) = #f
  alignment : (or/c #f 1 2 4 8 16) = #f
  malloc-mode : 
(or/c 'raw 'atomic 'nonatomic 'tagged
      'atomic-interior 'interior
      'zeroed-atomic 'zeroed-atomic-interior
      'stubborn 'uncollectable 'eternal)
   = 'atomic
The primitive type constructor for creating new C struct types. These types are actually new primitive types; they have no conversion functions associated. The corresponding Racket objects that are used for structs are pointers, but when these types are used, the value that the pointer refers to is used, rather than the pointer itself. This value is basically made of a number of bytes that is known according to the given list of types list.

If alignment is #f, then the natural alignment of each type in types is used for its alignment within the struct type. Otherwise, alignment is used for all struct type members.

The malloc-mode argument is used when an instance of the type is allocated to represent the result of a function call. This allocation mode is not used for an argument to a callback, because temporary space allocated on the C stack (possibly by the calling convention) is used in that case.

Changed in version 7.3.0.8 of package base: Added the malloc-mode argument.
Changed in version 8.14.0.4: Added the 'zeroed-atomic 'zeroed-atomic-interior allocation modes.

procedure

(_list-struct [#:alignment alignment    
  #:malloc-mode malloc-mode]    
  type ...+)  ctype?
  alignment : (or/c #f 1 2 4 8 16) = #f
  malloc-mode : 
(or/c 'raw 'atomic 'nonatomic 'tagged
      'atomic-interior 'interior
      'zeroed-atomic 'zeroed-atomic-interior
      'stubborn 'uncollectable 'eternal)
   = 'atomic
  type : ctype?
A type constructor that builds a struct type using make-cstruct-type function and wraps it in a type that marshals a struct as a list of its components. Note that space for structs must be allocated using malloc with malloc-mode; the converter for a _list-struct type immediately allocates and uses a list from the allocated space, so it is inefficient. Use define-cstruct below for a more efficient approach.

Changed in version 6.0.0.6 of package base: Added #:malloc-mode.

#:changed "8.14.0.4" Added the 'zeroed-atomic 'zeroed-atomic-interior allocation modes.

syntax

(define-cstruct id/sup ([field-id type-expr field-option ...] ...)
  property ...)
 
id/sup = _id
  | (_id _super-id)
     
field-option = #:offset offset-expr
     
property = #:alignment alignment-expr
  | #:malloc-mode malloc-mode-expr
  | #:property prop-expr val-expr
  | #:no-equal
  | #:define-unsafe
 
  offset-expr : exact-integer?
  alignment-expr : (or/c #f 1 2 4 8 16)
  malloc-mode-expr : 
(or/c 'raw 'atomic 'nonatomic 'tagged
      'atomic-interior 'interior
      'zeroed-atomic 'zeroed-atomic-interior
      'stubborn 'uncollectable 'eternal)
  prop-expr : struct-type-property?
Defines a new C struct type, but unlike _list-struct, the resulting type deals with C structs in binary form, rather than marshaling them to Racket values. The syntax is similar to define-struct, providing accessor functions for raw struct values (which are pointer objects); the _id must start with _, at most one #:offset can be supplied for a field, and at most one #:alignment or #:malloc-mode can be supplied. If no _super-id is provided, then at least one field must be specified.

The resulting bindings are as follows:

Objects of the new type are actually C pointers, with a type tag that is the symbol form of id or a list that contains the symbol form of id. Since structs are implemented as pointers, they can be used for a _pointer input to a foreign function: their address will be used. To make this a little safer, the corresponding cpointer type is defined as _id-pointer. The _id type should not be used when a pointer is expected, since it will cause the struct to be copied rather than use the pointer value, leading to memory corruption.

Field offsets within the structure are normally computed automatically, but the offset for a field can be specified with #:offset. Specifying #:offset for a field affects the default offsets computed for all remaining fields.

Instances of the new type are not normally Racket structure instances. However, if at least one #:property modifier is specified, then struct creation and coercions from _id variants wrap a non-NULL C pointer representation in a Racket structure that has the specified properties. The wrapper Racket structure also has a prop:cpointer property, so that wrapped C pointers can be treated the same as unwrapped C pointers. If a super-id is provided and it corresponds to a C struct type with a wrapper structure type, then the wrapper structure type is a subtype of super-id’s wrapper structure type. If a #:property modifier is specified, #:no-equal is not specified, and if prop:equal+hash is not specified as any #:property, then the prop:equal+hash property is automatically implemented for the wrapper structure type to use ptr-equal?.

If the first field is itself a C struct type, its tag will be used in addition to the new tag. This feature supports common cases of object inheritance, where a sub-struct is made by having a first field that is its super-struct. Instances of the sub-struct can be considered as instances of the super-struct, since they share the same initial layout. Using the tag of an initial C struct field means that the same behavior is implemented in Racket; for example, accessors and mutators of the super-struct can be used with the new sub-struct. See the example below.

Providing a super-id is shorthand for using an initial field named super-id and using _super-id as its type. Thus, the new struct will use _super-id’s tag in addition to its own tag, meaning that instances of _id can be used as instances of _super-id. Aside from the syntactic sugar, the constructor function is different when this syntax is used: instead of expecting a first argument that is an instance of _super-id, the constructor will expect arguments for each of _super-id’s fields, in addition for the new fields. This adjustment of the constructor is, again, in analogy to using a supertype with define-struct.

Structs are allocated using malloc with the result of malloc-mode-expr, which defaults to 'atomic. (This allocation mode does not apply to arguments of a callback; see also define-cstruct-type.) The default allocation of 'atomic means that the garbage collector ignores the content of a struct; thus, struct fields can hold only non-pointer values, pointers to memory outside the GC’s control, and otherwise-reachable pointers to immobile GC-managed values (such as those allocated with malloc and 'internal or 'internal-atomic).

As an example, consider the following C code:

  typedef struct { int x; char y; } A;

  typedef struct { A a; int z; } B;

  

  A* makeA() {

    A *p = malloc(sizeof(A));

    p->x = 1;

    p->y = 2;

    return p;

  }

  

  B* makeB() {

    B *p = malloc(sizeof(B));

    p->a.x = 1;

    p->a.y = 2;

    p->z   = 3;

    return p;

  }

  

  char gety(A* a) {

    return a->y;

  }

Using the simple _list-struct, you might expect this code to work:

(define makeB
  (get-ffi-obj 'makeB "foo.so"
    (_fun -> (_list-struct (_list-struct _int _byte) _int))))
(makeB) ; should return '((1 2) 3)

The problem here is that makeB returns a pointer to the struct rather than the struct itself. The following works as expected:

(define makeB
  (get-ffi-obj 'makeB "foo.so" (_fun -> _pointer)))
(ptr-ref (makeB) (_list-struct (_list-struct _int _byte) _int))

As described above, _list-structs should be used in cases where efficiency is not an issue. We continue using define-cstruct, first define a type for A which makes it possible to use makeA:

(define-cstruct _A ([x _int] [y _byte]))
(define makeA
  (get-ffi-obj 'makeA "foo.so"
    (_fun -> _A-pointer))) ; using _A is a memory-corrupting bug!
(define a (makeA))
(list a (A-x a) (A-y a))
; produces an A containing 1 and 2

Using gety is also simple:

(define gety
  (get-ffi-obj 'gety "foo.so"
    (_fun _A-pointer -> _byte)))
(gety a) ; produces 2

We now define another C struct for B, and expose makeB using it:

(define-cstruct _B ([a _A] [z _int]))
(define makeB
  (get-ffi-obj 'makeB "foo.so"
    (_fun -> _B-pointer)))
(define b (makeB))

We can access all values of b using a naive approach:

(list (A-x (B-a b)) (A-y (B-a b)) (B-z b))

but this is inefficient as it allocates and copies an instance of A on every access. Inspecting the tags (cpointer-tag b) we can see that A’s tag is included, so we can simply use its accessors and mutators, as well as any function that is defined to take an A pointer:

(list (A-x b) (A-y b) (B-z b))
(gety b)

Constructing a B instance in Racket requires allocating a temporary A struct:

(define b (make-B (make-A 1 2) 3))

To make this more efficient, we switch to the alternative define-cstruct syntax, which creates a constructor that expects arguments for both the super fields and the new ones:

(define-cstruct (_B _A) ([z _int]))
(define b (make-B 1 2 3))

Changed in version 6.0.0.6 of package base: Added #:malloc-mode.
Changed in version 6.1.1.8: Added #:offset for fields.
Changed in version 6.3.0.13: Added #:define-unsafe.
Changed in version 8.14.0.4: Added the 'zeroed-atomic 'zeroed-atomic-interior allocation modes.

procedure

(compute-offsets types [alignment declare])

  (listof exact-integer?)
  types : (listof ctype?)
  alignment : (or/c #f 1 2 4 8 16) = #f
  declare : (listof (or/c #f exact-integer?)) = '()
Given a list of types in a C struct type, return the offset of those types.

The types list describes a C struct type and is identical to the list in make-cstruct-type.

The C struct’s alignment is set with alignment The behavior is identical to make-cstruct-type.

Explicit positions can be set with declare. If provided, it is a list with the same length as as types. At each index, if a number is provided, that type is at that offset. Otherwise, the type is alignment bytes after the offset.

Examples:
> (compute-offsets (list _int _bool _short))

'(0 4 8)

> (compute-offsets (list _int _bool _short) 1)

'(0 4 8)

> (compute-offsets (list _int _int _int) #f (list #f 5 #f))

'(0 5 12)

Added in version 6.10.1.2 of package base.