3.7 C Struct Types
(make-cstruct-type types) → ctype? |
types : (listof ctype?) |
(_list-struct type ...+) → ctype? |
type : ctype? |
(define-cstruct id/sup ([field-id type-expr] ...)) | ||||||||||
|
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?: a predicate for the new type.
id-tag: the tag string object that is used with instances.
make-id : a constructor, which expects an argument for each type.
id-field-id : an accessor function for each field-id.
set-id-field-id! : a mutator function for each field-id.
id: structure-type information compatible with struct-out or match (but not define-struct); currently, this information is correct only when no super-id is specified.
Objects of the new type are actually C pointers, with a type tag that is a list that contains the string 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.
If the first field is itself a cstruct 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 cstruct field means that the same behavior is implemented in Racket; for example, accessors and mutators of the super-cstruct can be used with the new sub-cstruct. 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.
Note that structs are allocated as atomic blocks, which means that the garbage collector ignores their content. Currently, there is no safe way to store pointers to GC-managed objects in structs (even if you keep a reference to avoid collecting the referenced objects, a the 3m variant’s GC will invalidate the pointer’s value). Thus, only non-pointer values and pointers to memory that is outside the GC’s control can be placed into struct fields.
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)) |