2.4 Defining Foreign Types
syntax
(define-ffi2-type name-or-constructor parent-type option ...)
name-or-constructor = name | (name arg-id ...) option = #:tag tag | #:predicate predicate-expr | #:racket->c racket->c-expr | #:release release-expr | #:c->racket c->racket-expr tag = identifier | #f
If name-or-constructor is (name arg-id ...), then name is defined as a type constructor that receives expression arguments when applied in a type position, and the remainder of the define-ffi2-type can refer to the arg-ids to parameterize the definition over supplied values.
When parent-type is a struct_t or union_t form, a type (as opposed to a type constructor) is being defined, and no options are provided, then the parent-type affects the definitions created by define-ffi2-type. See struct_t and union_t for more information.
When parent-type is an array_t form, a type (as opposed to a type constructor) is being defined, and no options are provided other than #:tag, then the array_t also affects the definitions created by define-ffi2-type. See array_t for more information.
When parent-type is an immediate pointer type, a type (as opposed to a type constructor) is being defined, and no options are provided other than #:tag and #:predicate, then define-ffi2-type creates a new immediate pointer type that is a subtype of parent-type. The only predefined immediate pointer types are ptr_t and ptr_t/gcable (and they are treated the same as a parent type). The Racket representation of the new type is a pointer object. Unless tag is supplied as #f, each pointer’s tags add tag (if present) or name to the end of the tags for parent-type, which creates a pointer subtype. If tag is provided as #f, Racket representation of the new type is a generic pointer with no tags. The type name/gcable is defined in addition to name, and name/gcable is equivalent to (gcable_t name).
When parent-type is an scalar type, a type (as opposed to a type constructor) is being defined, and no options are provided, the defined name is a scalar type.
The #:tag option can be used only when parent-type is an immediate pointer type or array_t form. In the case of an array_t form with a constant size, #:tag cannot be mixed with any other option form.
In all cases, the new type name (or the type that it constructs) has the same C representation as parent-type. The Racket representation can be adjusted via #:racket->c and #:c->racket options, which often need accompanying #:predicate and #:release procedures:
#:predicate provides a predicate procedure as the result of predicate-expr. This predicate is used when checks are enabled for Racket values to be converted to C for the type name, but the predicate can be skipped for unsafe mode as enabled via (#%declare #:unsafe) at the use of the type. The predicate determines whether a value is suitable as an argument to a procedure provided by #:racket->c. If #:predicate is not provided, then the predicate associated with parent-type is used.
#:racket->c provides a conversion procedure toward C as the result of racket->c-expr. This converter is applied to a Racket value that is supplied for a name type. The result of conversion should be a Racket representation for parent-type. If #:racket->c is not provided, conversion is the identity procedure.
#:release provides a procedure that finalizes conversion from Racket to C. The procedure is applied to the result of parent-type’s release procedure after the C value is delivered (e.g., passed in a foreign procedure call that has returned). For base pointer types, the release procedure is black-box, which is useful because it keeps a pointer live in case it is subject to garbage collection. A release procedure could explicitly deallocate a pointer that was allocated by the #:racket->c procedure, but beware that a release procedure is not called if control somehow escapes or the current thread is forcibly terminated.
#:c->racket provides a conversion procedure toward Racket as the result of c->racket-expr. This converter is applied to a Racket representation of parent-type as extracted from a C representation. The result of conversion should be a Racket representation for name. If #:c->racket is not provided, conversion is the identity function.
> (define-ffi2-type percentage_t double_t #:predicate (lambda (v) (and (real? v) (<= 0.0 v 100.0))) #:racket->c (lambda (v) (/ v 100.0)) #:c->racket (lambda (v) (* v 100.0))) > (define p (ffi2-malloc double_t)) > (ffi2-set! p double_t 0.5) > (ffi2-ref p percentage_t) 50.0
> (ffi2-set! p percentage_t 25.5) > (ffi2-ref p double_t) 0.255
> (define-ffi2-type percentage_box_t ptr_t #:predicate (lambda (bx) (percentage_t? (unbox bx))) #:racket->c (lambda (bx) (define ptr (ffi2-malloc #:gcable-immobile percentage_t)) (ffi2-set! ptr percentage_t (unbox bx)) ptr) #:c->racket (lambda (ptr) (box (ffi2-ref ptr percentage_t)))) > (define p (ffi2-malloc #:gcable-traced ptr_t)) > (ffi2-set! p percentage_box_t (box 50.5)) > (ffi2-ref p percentage_box_t) '#&50.5
> (ffi2-ref (ffi2-ref p ptr_t) double_t) 0.505
> (define-ffi2-type (offset_double_t delta) double_t #:c->racket (lambda (v) (+ v delta)) #:racket->c (lambda (v) (- v delta)))
> (define-ffi2-type posn_t (struct_t [x (offset_double_t 1.0)] [y (offset_double_t 2.0)])) > (define p (posn_t 10.0 20.0)) > (ffi2-ref p double_t 0) 9.0
> (ffi2-ref p double_t 1) 18.0
> (set-posn_t-x! p 100.0) > (posn_t-x p) 100.0
> (ffi2-ref p double_t 0) 99.0
syntax
(define-ffi2-type-syntax name proc-expr)
(define-ffi2-type-syntax (name arg-id ...) body ...)
syntax
(ffi2-sizeof type)
syntax
(ffi2-offsetof type field-id)
syntax
(ffi2-is-a? expr type)
Note that (ptr_t/gcable? expr) is a more specific test than (ffi2-is-a? expr ptr_t/gcable), because ptr_t/gcable accepts any pointer object for conversion to C, while ptr_t/gcable? recognizes only pointers that are allowed to reference memory managed by Racket’s garbage collection.
syntax
(define-ffi2-enum name parent_type enum-case ...)
enum-case = symbol | symbol = exact-integer
The set of symbols must be unique, but multiple symbols can make to the same integer, and conversion from C chooses the last matching symbol in the declaration sequence. When a C representation to convert is not an integer value for one of the symbols, then the value is kept in integer form.
> (define-ffi2-enum shape_t int_t circle square triangle) > (ffi2-cast 'triangle #:from shape_t #:to int_t) 2
> (ffi2-cast 1 #:from int_t #:to shape_t) 'square
> (ffi2-cast -1 #:from int_t #:to shape_t) -1
ffi2 type/abi
(system-type-case key [(val ...) type/abi] [else type/abi])
key = os | os* | arch | word
In the case of types, each right-hand side type/abi must be a scalar type, such as int_t or float_t. A system-type-case type is also scalar, since it selects among scalar types.