On this page:
define-ffi2-type
define-ffi2-type-syntax
ffi2-sizeof
ffi2-offsetof
ffi2-is-a?
define-ffi2-enum
system-type-case
9.2.0.3

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
Defines a type name or a type constructor name that is (or creates) an alias or extension of parent-type.

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.

Examples:
> (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 ...)
Like define-syntax, but binds name as a macro that is expanded in type positions, instead of expression positions.

syntax

(ffi2-sizeof type)

Returns the number of bytes used for the C representation of type.

syntax

(ffi2-offsetof type field-id)

Returns the number of bytes in the C representation of type that precede the field named field-id. The type must be have the C representation of a struct_t or union_t type; the result is always 0 in the case of a union_t type.

syntax

(ffi2-is-a? expr type)

Returns #t or #f indicating whether the result of expr is a Racket representation for 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
Defines name as a new type that extends parent_type, which should be an integer type. The Racket representation of the new type is a symbol that is one of the symbol identifiers listed as an enum-case. Each symbol is converted to the corresponding exact-integer if supplied, otherwise it is converted to 0 for the first symbol or one more than the preceding symbol’s value.

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
Describes a type with a platform-specific representation or a platform-specific choice of procedure ABI, enabling a compile-time (later than expand-time) choice. The symbol form of key corresponds to a symbol argument to system-type, and each val must be a potential result: an identifier for keys other than word, or either 32 or 64 in the case of 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.