3.7 C Struct Types
procedure
(make-cstruct-type types [abi alignment]) → ctype?
types : (listof ctype?) abi : (or/c #f 'default 'stdcall 'sysv) = #f alignment : (or/c #f 1 2 4 8 16) = #f
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.
procedure
(_list-struct [ #:alignment alignment] type ...+) → ctype? alignment : (or/c #f 1 2 4 8 16) = #f type : ctype?
syntax
(define-cstruct id/sup ([field-id type-expr] ...) property ...)
id/sup = _id | (_id super-id) property = #:alignment alignment-expr | #:property prop-expr val-expr | #:no-equal
The resulting bindings are as follows:
_id : the new C type for this struct.
_id-pointer: a pointer type that should be used when a pointer to values of this struct are used.
_id-pointer/null: like _id-pointer, but allowing NULL pointers (as represented on the Racket side by #f).
id?: a predicate for the new type.
id-tag: the tag object that is used with instances. The tag object may be the symbol form of id or a list of symbols containing the id symbol and other symbols, such as the super-id symbol.
make-id : a constructor, which expects an argument for each field.
id-field-id : an accessor function for each field-id; if the field has a C struct type, then the result of the accessor is a pointer to the field within the enclosing structure, rather than a copy of the field.
set-id-field-id! : a mutator function for each field-id.
id: structure-type information compatible with struct-out or match (but not struct or define-struct); currently, this information is correct only when no super-id is specified.
id->list, list->id : a function that converts a struct into a list of field values and vice versa.
id->list*, list*->id : like id->list, list->id, but fields that are structs are recursively unpacked to lists or packed from lists.
struct:cpointer:id: only when a #:property is specified —
a structure type that corresponds to a wrapper to reflect properties (see below).
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.
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 as atomic blocks, which means that the garbage collector ignores their content. 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))