DRAFT. This egg has not been published yet and its API will change. If you want to beta test, provide feedback, and help shape the final API, join ##jiffi on Libera.Chat or email the author.

Jiffi

High-level helpers for writing foreign function interface (FFI) bindings to C libraries.

If you prefer to automatically generate FFI bindings for simple C/C++ code, try the bind egg instead.

Hint If you are new to Jiffi, the best place to start is the Getting Started with Jiffi guide. The wiki page you are reading now contains detailed reference documentation for every feature in Jiffi, which is a lot of information! After reading the Getting Started guide to gain a basic understanding, come back to this page to look up details as needed.
Project / Source Code
https://gitlab.com/jcroisant/jiffi
Issue Tracker
https://gitlab.com/jcroisant/jiffi/issues
Author
John Croisant
License
BSD

Table of Contents (skip)

  1. Jiffi
  2. About
  3. Installation
  4. Example
  5. Functions and Macros
    1. define-binding
    2. define-binding** (2)
    3. foreign-lambda**
  6. Constants and Enums
    1. define-foreign-values
    2. define-enum-group
      1. Enum converters
      2. Enum requirements
    3. define-enum-packer
      1. Enum packer
    4. define-enum-unpacker
      1. Enum unpacker
  7. Armor
    1. define-armor-type
      1. Armor wrapper
      2. Armor unwrapper
    2. armor-printer
      1. define-armor-printer
    3. Generic armor operations
      1. armor?
      2. armor-address
      3. armor-eq?
      4. armor-null?
      5. nullify-armor!
      6. Armor parents and children
        1. armor-parent
        2. armor-parent-set!
        3. armor-tracks-children?
      7. Armor thread safety
        1. make-armor-lock
        2. armor-has-lock?
        3. armor-lock-set!
  8. Structs and Unions
    1. define-struct-allocators
      1. Struct freer
      2. Struct memory allocators
      3. Struct makers
    2. struct-copier
    3. define-struct-accessors
      1. struct-getter
      2. struct-setter
  9. Arrays of Structs and Unions
    1. define-array-allocators
      1. Array armor length slot
      2. Array freer
      3. Array memory allocators
      4. Array makers
    2. define-array-accessors
      1. Array item getter
      2. Array item setter
      3. Array item mapper
      4. Array item iterator
      5. Array item pointer getter
      6. Array item pointer mapper
      7. Array item pointer iterator

About

Jiffi is designed specifically for writing FFI bindings by hand, rather than automatic generation. Its design goals are:

  1. Usability: Bindings should be easy to use. They should follow Scheme idioms, and not require the user to understand low-level details about C memory management, pointers, etc.
  2. Safety: Bindings should, by default, protect users against type errors and memory errors at the C level. Writing unsafe code should require a deliberate choice.
  3. Maintainability: The binding code itself should be easy for its authors to understand, extend, and debug. It should be clear, explicit, predictable, and greppable.
  4. Flexibility: Jiffi should be flexible enough able to handle the most common 80% of FFI use cases, and give binding authors a high degree of control over their API style.
  5. Performance: Bindings should be as efficient as possible without sacrificing the other goals.

Jiffi is especially well-suited for writing FFI bindings for C libraries that are so large that it is difficult for a human to maintain bindings using CHICKEN's FFI primitives, and so syntactically complex that the bind egg cannot parse it and generate bindings automatically. Jiffi evolved from the FFI helpers that were used to write the sdl2 egg.

Jiffi is also good for beginners who are not familiar with the intricacies of CHICKEN's FFI features, or who are not very comfortable with C concepts like pointers and memory management, which can cause crashes and security vulnerabilities if not done correctly. Jiffi helps you create easy-to-use, safe, and efficient FFI bindings, even if you are not a C expert.

All of Jiffi's features are built using the FFI primitives provided by CHICKEN, so they are cross-compatible. The documentation for each macro describes which primitives it uses, so you can understand what is happening behind the scenes. In most cases you can use one feature of Jiffi without using the other features, although some are closely coupled out of necessity.

Installation

Jiffi has not yet been published, so you must clone or download the git repository, then run chicken-install within the directory.

Jiffi requires CHICKEN 4.13 or higher. It is compatible with both CHICKEN 4 and CHICKEN 5.

If you define any thread-safe armors, you should add srfi-18 to your dependencies. (This is not necessary for CHICKEN 4, which has SRFI-18 built in.)

Example

A thorough example can be found in the tests directory of the source code:

Snippets of this example are included in this wiki documentation.

Functions and Macros

define-binding

[syntax] (define-binding ...)

Defines a Scheme procedure which calls a C function or C macro. This is syntactic sugar for define and foreign-lambda.

Usage:

(define-binding (SCHEME-NAME FUNC_NAME)      ; or just SCHEME-NAME
  return: RETURN-TYPE                        ; optional
  args: ((ARG-TYPE ARG_NAME) ...))           ; optional

SCHEME-NAME is the procedure name to define in Scheme. FUNC_NAME is a non-quoted symbol or string containing the name of the C function or C macro. If SCHEME-NAME and FUNC_NAME are the same, you can write SCHEME-NAME instead of (SCHEME-NAME FUNC_NAME).

RETURN-TYPE is a foreign type specifier describing the return value of the function.

Each ARG-TYPE is a foreign type specifier describing that argument's type.

Each ARG_NAME is a non-quoted symbol describing the argument, usually the argument name in C. This is not actually used in the macro, but it makes it easier for you to see that the binding has the correct number and order of arguments, so mistakes are less likely.

If the return: clause is omitted, the return type is void. If the args: clause is omitted, the procedure accepts no arguments.

Examples:

;; C function and macro definitions
(foreign-declare "
FOO_Event* FOO_CreateKeyEvent(FOO_KeyCode code, FOO_KeyMod mods) {
  FOO_Event* ev = malloc(sizeof(FOO_Event));
  ev->type = FOO_EVENT_TYPE_KEY;
  ev->key.code = code;
  ev->key.mods = mods;
  return ev;
}

#define FOO_ADD1(x) ((x) + 1)
")

;; Binding to a C function, with scheme-style name, and custom foreign
;; types (see examples in the "Armor wrapper" and "Armor unwrapper"
;; sections) to automatically convert return value and args.
(define-binding (create-key-event FOO_CreateKeyEvent)
  return: FOO_KeyEvent*
  args: ((FOO_KeyCode code)
         (FOO_KeyMod mods)))

;; Binding to a C macro
(define-binding FOO_ADD1
  return: int
  args: ((int x)))

(define ev (create-key-event 'a '(lctrl)))
(key-event-code ev) ; ⇒ 'a
(key-event-mods ev) ; ⇒ '(lctrl)

(FOO_ADD1 4)        ; ⇒ 5

define-binding** (2)

[syntax] (define-binding** ...)

Like define-binding, except that it is based on foreign-lambda**, so you can specify a custom body of C code, or construct the function body at macro expansion time.

Usage:

(define-binding** SCHEME-NAME
  return: RETURN-TYPE                   ; optional
  args: ((ARG-TYPE ARG_NAME) ...)       ; optional
  BODY-CLAUSE
  ...)

SCHEME-NAME is the procedure name to define in Scheme.

If the return: clause is omitted, the return type is void. If the args: clause is omitted, the function accepts no arguments.

Each ARG_NAME must be a non-quoted symbol that is a valid C variable name. You can then use ARG_NAME within the function body.

Each BODY-CLAUSE becomes a separate line of the function body. It can either be a string, or a list of the form (FORMAT-STRING FORMAT-ARG ...), which are arguments passed to sprintf to construct the string. See foreign-lambda** for more information.

For obscure technical reasons, you must use C_return(...) instead of the normal return statement to return a value from C.

Example:

;; Create a binding to arbitrary C code.
(define-binding** squaref
  return: float
  args: ((float x))
  "C_return(x * x);")

(squaref 8.0)  ; ⇒ 64.0

;; Expands into a define-binding** that gets the value of a struct
;; field. This is similar to how struct-getter is implemented.
(define-syntax define-struct-getter
  (syntax-rules ()
    ((define-struct-getter GETTER
      STRUCT_NAME FIELD-NAME FIELD-TYPE)
     (define-binding** GETTER
       return: FIELD-TYPE
       args: (((c-pointer STRUCT_NAME) obj))
       ;; These could easily be one line, but they are split to
       ;; demonstrate that you can have multiple body clauses:
       "C_return("
       ("obj->~A" 'FIELD-NAME)
       ");"))))

(define-struct-getter event-type
  "FOO_Event" "type" int)

;; Expands to:
;;
;;   (define-binding** event-type
;;     return: int
;;     args: (((c-pointer "FOO_Event") obj))
;;     "C_return("
;;     ("obj->~A" '"type")
;;     ");"))));;
;;
;; Which produces the C function body:
;;
;;   C_return(
;;   obj->type
;;   );

foreign-lambda**

[syntax] (foreign-lambda** ...)

Like foreign-lambda*, except the function body strings can be constructed dynamically at macro expansion time using sprintf. This is a building block for higher-level macros that generate function bodies based on the macro's arguments.

Usage:

(foreign-lambda**
 RETURN-TYPE
 ((ARG-TYPE ARG_NAME)
  ...)
 BODY-CLAUSE
 ...)

RETURN-TYPE is a foreign type specifier describing the return value.

Each ARG-TYPE is a foreign type specifier describing the argument. Each ARG_NAME is a non-quoted symbol which is used as the variable name within the function body. It must be a valid C variable name.

Each BODY-CLAUSE becomes a separate line of the function body. It can either be a string, or a list of the form (FORMAT-STRING FORMAT-ARG ...), which are arguments passed to sprintf to construct the string.

Because the body string is constructed at macro expansion time, each FORMAT-ARG must be an expression that can be evaluated at macro expansion time in the default environment, such as a quoted symbol, a string, a number, or a call to a built-in procedure.

For obscure technical reasons, you must use C_return(...) instead of the normal return statement to return a value from C.

Examples:

(define return-foo
  (foreign-lambda**
   c-string
   () ; no arguments
   "C_return(\"foo\");"))

(return-foo)   ; ⇒ "foo"

;; Silly example that expands into a foreign-lambda** that performs a
;; simple integer math operation described by the macro caller.
;; X and Y are evaluated at macro-expansion time.
(define-syntax c-math
  (syntax-rules ()
    ((math-fn X OPERATOR Y)
     (foreign-lambda**
      int
      () ; no arguments
      ("C_return(~A ~A ~A);" X 'OPERATOR Y)))))

;; Body becomes "C_return(40 + 2);"
(define return-42 (c-math (* 8 5) + (/ 4 2)))

(return-42)    ; ⇒ 42

Constants and Enums

define-foreign-values

[syntax] (define-foreign-values ...)

Defines (and optionally exports) variables with the values of C constants or expressions. Each value can be the name of a const, enum, or #define; or it can be a string containing a C expression which will be evaluated at run time.

If you want to define procedures to convert integer constants to symbols, and vice versa, use define-enum-group instead.

Usage:

(define-foreign-values
  export: EXPORT         ; optional
  type: FOREIGN-TYPE
  (VAR EXPRESSION)       ; or just VAR
  ...)

If EXPORT is #t, every VAR will be automatically exported from the current module, so you don't need to write a long (export ...) form with all the variables. If EXPORT is #f or the keyword clause is omitted, they are not automatically exported.

FOREIGN-TYPE is a foreign type specifier that describes all the foreign values.

Each VAR is a variable name to define in Scheme.

Each EXPRESSION is a non-quoted symbol or string containing the name of a foreign constant in C, or a string containing a C expression.

If VAR and EXPRESSION are the same, you can write VAR instead of (VAR EXPRESSION). This must be a variable name (non-quoted symbol), not a string.

Examples:

;; C enum definitions
(foreign-declare "
#define FOO_BAR  \"Bar!\"
#define FOO_BUZZ \"Buzz!\"

const float FOO_PHI = 2.718281828459045;
const float FOO_PI  = 3.141592653589793;

typedef enum {
  FOO_BLEND_NONE = 0,
  FOO_BLEND_ADD = 1,
  FOO_BLEND_SUB = 2,
  FOO_BLEND_MUL = 4
} FOO_BlendMode;

#define FOO_ADD1(x) ((x) + 1)
")

;; #define
(define-foreign-values
  export: #t
  type: c-string
  FOO_BAR
  FOO_BUZZ)

;; const
(define-foreign-values
  export: #t
  type: float
  (φ FOO_PHI)
  (π FOO_PI))

;; enum
(define-foreign-values
  export: #t
  type: int
  FOO_BLEND_NONE
  FOO_BLEND_ADD
  FOO_BLEND_SUB
  FOO_BLEND_MUL)

;; C expression
(define-foreign-values
  export: #t
  type: double
  (four "1.5 + 2.5")
  (five "FOO_ADD1(4)")
  (sqrt2 "sqrt(2.0)"))

FOO_BAR        ; ⇒ "Bar!"
φ              ; ⇒ 2.71828174591064
FOO_BLEND_SUB  ; ⇒ 2
four           ; ⇒ 4.0
five           ; ⇒ 5.0
sqrt2          ; ⇒ 1.4142135623731

define-foreign-values is based on foreign-value. It is basically equivalent to:

(export VAR ...)
(define VAR (foreign-value EXPRESSION FOREIGN-TYPE))
...

define-enum-group

[syntax] (define-enum-group ...)

Defines procedures to convert integer constants to symbols, and vice versa, and optionally defines a variable for each integer constant. Also generates type declarations for the converter procedures.

If you only want to define variables, and don't need symbol conversion procedures, see define-foreign-values.

Usage:

(define-enum-group
  type: FOREIGN-TYPE
  vars: VARS-MODE             ; optional, default: define

  symbol->int: SYMBOL->INT    ; optional
  allow-ints: ALLOW-INTS      ; optional

  int->symbol: INT->SYMBOL    ; optional

  ;; Optional `requires:' clause for enum requirements
  ((requires: SCHEME-FEATURE-CLAUSE "C_PREPROCESSOR_EXPR")
   ;; If VAR and CONSTANT are the same, you can write
   ;; VAR instead of (VAR CONSTANT)
   (SYMBOL (VAR CONSTANT) FLAG ...)
   ...)

  ...)

FOREIGN-TYPE is a foreign integer type specifier that will be used for all the constants. The type specifier integer is a safe choice, but if you know all the constants are in the range -1,073,741,824 to 1,073,741,823 inclusive, you can safely use int which may be more efficient.

VARS-MODE specifies whether variables for the enums should be defined and/or exported from the current module, using define-foreign-values. If VARS-MODE is define or the clause is omitted, each VAR will be defined but not automatically exported, equivalent to (define-foreign-values export: #f ...). If VARS-MODE is export, every VAR will be defined and also automatically exported, equivalent to (define-foreign-values export: #t ...). If VARS-MODE is #f, variables will not be defined or exported. This allows you to define the enum converter procedures without defining any variables.

SYMBOL->INT and INT->SYMBOL are procedure names to define as enum converters. If any is #f or its keyword clause is omitted, that procedure will not be defined.

If ALLOW-INTS is #t, SYMBOL->INT will also accept integers, not only symbols. Integers are returned immediately, without any checks. If ALLOW-INTS is #f or the keyword clause is omitted, then if SYMBOL->INT is called with an integer, it will invoke its not-found-callback (if there is one) or signal an exception.

The optional (requires: SCHEME-FEATURE-CLAUSE "C_PREPROCESSOR_EXPR") clause is used to specify enum requirements. If the (requires: ...) clause is omitted, the enums in the list will always be included in the group.

Each (SYMBOL (VAR CONSTANT) FLAG ...) or (SYMBOL VAR FLAG ...) defines the relationship between a symbol and an integer constant.

Each SYMBOL is a non-quoted symbol. It will be a valid argument to SYMBOL->INT. And it will be a possible return value from INT->SYMBOL, unless it is marked with the alias flag.

Each VAR is a variable name to define in Scheme, with the value of CONSTANT.

Each CONSTANT is a non-quoted symbol or string containing the name of a C integer constant, such as a global const, #define, or enum value. (It is not necessary for all the CONSTANTs to be from the same enum statement.) Or, it can be a string containing an integer constant expression, which must be a valid case label in a C switch statement, such as "1 + 1" or "FOO - 1". The value of each constant or expression must be unique within this enum group, unless it is marked with the alias flag.

If VAR and CONSTANT are the same, you can write VAR instead of (VAR CONSTANT). This must be a variable name (non-quoted symbol), not a string.

FLAGs are optional non-quoted symbols marking this symbol/constant relationship as special in some way. The following flag is currently recognized (other flags are ignored):

alias
This constant has the same integer value as another constant in this group. This constant will be omitted from INT->SYMBOL to avoid a C compiler error about duplicate case values.

Basic example:

;; C enum definitions
(foreign-declare "
typedef enum {
  FOO_BLEND_NONE = 0,
  FOO_BLEND_ADD = 1,
  FOO_BLEND_SUB = 2,
  FOO_BLEND_MUL = 4
} FOO_BlendMode;
")

(define-enum-group
  type: int
  vars: export
  symbol->int: blend-mode->int
  int->symbol: int->blend-mode
  ((none BLEND_NONE)
   (add  BLEND_ADD)
   (sub  BLEND_SUB)
   (mul  BLEND_MUL)))

(blend-mode->int 'sub)      ; ⇒ 2
(int->blend-mode BLEND_SUB) ; ⇒ 'sub

(blend-mode->int 'zzz)   ; Error: unrecognized enum symbol: zzz
(int->blend-mode 42)     ; Error: unrecognized enum value: 42

;; Using not-found-callbacks:
(blend-mode->int 'zzz (lambda (sym) -1))     ; ⇒ -1
(int->blend-mode 42   identity)              ; ⇒ 42

Advanced example (renaming, aliases, requirements):

;; C enum definitions
(foreign-declare "
#define FOO_VERSION 110

typedef enum {
  POWER_EMPTY = 0,
  POWER_NONE = 0, /* alias */
  POWER_LOW = 1,
  POWER_HIGH,
} PowerLevel;
")

;; Or compile with flag: -D foo-1.1+
(begin-for-syntax
 (register-feature! 'foo-1.1+))

(define-enum-group
  type: int
  vars: export
  symbol->int: power-level->int
  allow-ints: #t
  int->symbol: int->power-level

  ;; These enums are always included.
  ((empty (power/empty FOO_POWER_EMPTY))
   (none  (power/none  FOO_POWER_NONE)  alias)
   (low   (power/low   FOO_POWER_LOW))
   (high  (power/high  FOO_POWER_HIGH)))

  ;; Feature ID foo-1.1+ is registered above, so these requirements
  ;; are satisfied, so this enum will be included.
  ((requires: foo-1.1+ "FOO_VERSION >= 110")
   (wired (power/wired FOO_POWER_WIRED)))

  ;; These requirements are not satisfied, so this will be omitted.
  ((requires: foo-1.2+ "FOO_VERSION >= 120")
   (charge (power/charge FOO_POWER_CHARGE))))

;; `FOO_POWER_LOW' was renamed to `power/low'
power/low                     ; => 1
FOO_POWER_LOW                 ; Error: unbound variable: FOO_POWER_LOW

;; power/none is an alias, so it is omitted from int->power-level.
;; power/empty has the same integer value, so 'empty is returned.
(int->power-level power/none) ; ⇒ 'empty

;; `(requires: foo-1.2+ "FOO_VERSION >= 120")` was not satisfied.
power/charge                  ; Error: unbound variable: power/charge
(power-level->int 'charge)    ; Error: unrecognized enum symbol: charge

;; `allow-ints: #t` so power-level->int accepts integer args.
(power-level->int 42)         ; ⇒ 42

define-enum-group is based on foreign-value and foreign-primitive. Converting from a symbol to an integer uses a Scheme case form. Converting from an integer to a symbol uses a C switch statement.

Enum converters

The enum converter procedures defined with define-enum-group have the following interfaces:

(SYMBOL->INT input #!optional not-found-callback) → integer
(INT->SYMBOL input #!optional not-found-callback) → symbol

not-found-callback is an optional procedure of one argument. If a converter does not recognize the input, the converter returns the result of calling (not-found-callback input). The callback should either return a value of the correct type (integer or symbol), or else signal an exception. If not-found-callback is #f or omitted, the converter will signal an exception if the input is unrecognized.

If you passed allow-ints: #t to define-enum-group, then SYMBOL->INT will accept either a symbol or an integer. If it is passed an integer, it returns that integer immediately. It does not check whether the integer is one of the constants in this group. This allows users to provide the integer constant directly, for optimization or to handle special cases. But, it also allows users to pass invalid enum values, which might possibly cause the software to malfunction. You must decide whether flexibility or safety is more important for this enum group.

Enum requirements

In some situations, certain enum constants are not always defined. For example, maybe certain constants are defined only in the newest version of the C library, or defined only when the C library is compiled with an optional feature, or defined only when compiled for a certain operating system or hardware architecture.

If you try to include constants that are not defined, your software will have a C compiler error like "error: ‘FOO’ undeclared" or "error: use of undeclared identifier 'FOO". You can solve this by using a (requires:) clause to specify the conditions when certain constants should be included in the enum group:

(define-enum-group
  ;; (keywords args omitted for brevity)

  ((requires: SCHEME-FEATURE-CLAUSE "C_PREPROCESSOR_EXPR")
   (SYMBOL (VAR CONSTANT) FLAG ...)
   ...)
  ...)

You can pass multiple lists of constants, and each list can have different requirements. The constants in each list will be included in the enum group if its requirements are satisfied at compile time. If a list of constants does not have a (requires:) clause, those constants will always be included in the group.

Each SCHEME-FEATURE-CLAUSE is a feature identifier or other clause that is allowed in cond-expand, in the code (cond-expand (SCHEME-FEATURE-CLAUSE ...)).

Each "C_PREPROCESSOR_EXPR" is a string containing an C preprocessor expression that is allowed in the C code:

#if C_PREPROCESSOR_EXPR
...
#endif

SCHEME-FEATURE-CLAUSE and "C_PREPROCESSOR_EXPR" should both have the same meaning. In other words, they should both pass when the enums are defined, and they should both fail when the enums are not defined. There should never be a situation where only one passes.

define-enum-packer

[syntax] (define-enum-packer ...)

Defines a procedure that will "pack" a list of enums (or a single enum) into a single integer, aka a bitfield. The packer converts each enum into an integer, then combines them using bitwise-ior. This supplements define-enum-group for cases where the enums are bitmasks or flags.

See also define-enum-unpacker to go in the opposite direction, converting an integer into a list of symbols.

Usage:

(define-enum-packer PACKER
  (SYMBOL->INT)
  allow-ints: ALLOW-INTS)     ; optional

PACKER is the procedure name to define as the enum packer.

SYMBOL->INT is an existing procedure that converts a symbol into an integer value, such as a procedure defined with define-enum-group. It must accept a second argument, which will be the not-found-callback procedure passed to the packer, or #f if no callback was passed to the packer.

If ALLOW-INTS is #t, the packer will accept integers as well as symbols. If the packer encounters an integer, either in the list of enums or as a single argument, the integer will be used without trying to convert it. This allows users to pack the bitfield manually, for optimization or special cases. If ALLOW-INTS is #f or the keyword clause is omitted, then the packer will always try to convert the enums using SYMBOL->INT regardless of type.

Example:

(foreign-declare "
typedef enum {
  FOO_KMOD_NONE   = 0b0000,
  FOO_KMOD_LCTRL  = 0b0001,
  FOO_KMOD_RCTRL  = 0b0010,
  FOO_KMOD_CTRL   = 0b0011
} FOO_KeyMod;
")

(define-enum-group
  type: int
  symbol->int: keymod->int
  int->symbol: int->keymod
  ((none   FOO_KMOD_NONE)
   (lctrl  FOO_KMOD_LCTRL)
   (rctrl  FOO_KMOD_RCTRL)
   (ctrl   FOO_KMOD_CTRL)))

(define-enum-packer pack-keymods
  (keymod->int)
  allow-ints: #t)

(pack-keymods '(lctrl))            ; ⇒ 1
(pack-keymods 'lctrl)              ; ⇒ 1
(pack-keymods '(rctrl lctrl))      ; ⇒ 3
(pack-keymods '(lctrl 6))          ; ⇒ 7
(pack-keymods '())                 ; ⇒ 0
(pack-keymods 42)                  ; ⇒ 42

(pack-keymods '(lctrl foo))        ; error!
(pack-keymods '(lctrl foo)
              (lambda (sym) 16))   ; ⇒ 17

Enum packer

An enum packer procedure defined with define-enum-packer has the following interface:

(PACKER enums #!optional not-found-callback) → integer

enums can be a list of symbols, or a single symbol. If the packer was defined with allow-ints: #t, the list can also contain integers, and enums can also be a single integer; the integer(s) will be used as-is without passing them to SYMBOL->INT.

not-found-callback is an optional procedure that will be passed as the second argument to SYMBOL->INT. Assuming SYMBOL->INT was defined with define-enum-group, the callback will be invoked if an enum is not recognized. The callback should either return an integer or signal an exception.

define-enum-unpacker

[syntax] (define-enum-unpacker ...)

Defines a procedure that will "unpack" an integer (a bitfield) into a list of enum symbols. This supplements define-enum-group in cases where the enums are bitmasks or flags.

See also define-enum-packer to go in the opposite direction, converting a list of symbols into an integer.

Usage:

(define-enum-unpacker UNPACKER
  (INT->SYMBOL)
  masks: BITMASKS)

UNPACKER is the procedure name to define as the enum unpacker.

INT->SYMBOL is an existing procedure that converts an integer value (bitmask) into a symbol, such as a procedure defined with define-enum-group.

BITMASKS is an expression that evaluates to a list of all the integer bitmasks that should be recognized. The expression will be evaluated once, and each bitmask will be converted using INT->SYMBOL once, at the time UNPACKER is defined.

Hint Bitmasks that have a value of 0 (zero) will always match, so it is not useful to include them in the BITMASKS list.

Example:

(foreign-declare "
typedef enum {
  FOO_KMOD_NONE   = 0b0000,
  FOO_KMOD_LCTRL  = 0b0001,
  FOO_KMOD_RCTRL  = 0b0010,
  FOO_KMOD_CTRL   = 0b0011
} FOO_KeyMod;
")

(define-enum-group
  type: int
  symbol->int: keymod->int
  int->symbol: int->keymod
  ((none  FOO_KMOD_NONE)
   (lctrl FOO_KMOD_LCTRL)
   (rctrl FOO_KMOD_RCTRL)
   (ctrl  FOO_KMOD_CTRL)))

(define-enum-unpacker unpack-keymods
  (int->keymod)
  masks: (list FOO_KMOD_LCTRL
               FOO_KMOD_RCTRL
               FOO_KMOD_CTRL))

(unpack-keymods FOO_KMOD_NONE)     ; ⇒ '()
(unpack-keymods FOO_KMOD_LCTRL)    ; ⇒ '(lctrl)
(unpack-keymods FOO_KMOD_CTRL)     ; ⇒ '(lctrl rctrl ctrl)

Enum unpacker

An enum unpacker procedure defined with define-enum-unpacker has the following interface:

(UNPACKER bitfield) → list of symbols

An unpacker compares bitfield (an integer) to each bitmask in the BITMASKS list. If a bitmask matches (see below), the result of (INT->SYMBOL bitmask) is included in the list of symbols returned. The list of symbols will be in the same relative order as BITMASKS.

Each bitmask matches only if every true bit in the bitmask is also true in the bitfield. Expressed in Scheme: (= bitmask (bitwise-and bitfield bitmask)). Expressed in C: bitmask == (bitfield & bitmask).

Armor

Armor types are record types that are used to wrap bare data, such as pointers, locatives, or blobs. Using armor provides protection against certain kinds of bugs, crashes, and security vulnerabilities. Armor also makes debugging easier, and provides extra functionality. But, it requires more memory and CPU time than using bare data, so you may want to use bare data in performance-critical situations.

See "Why should I use armor?" in the Getting Started guide for more information.

Armor types can be used to wrap C structs/unions, or arrays of C structs/unions. You can define an armor type for each struct, union, and array type in the C library, then use the macros in the "Structs and Unions" or "Arrays of Structs and Unions" sections to define additional procedures specific to those use cases.

define-armor-type

[syntax] (define-armor-type ...)

Defines a record type that can wrap a C struct/union or an array of structs/unions, and defines some basic procedures related to the record type. Also generates type declarations for those procedures, except for extra slot getters and setters (if there are any).

Usage:

(define-armor-type ARMOR-NAME
  pred:     PRED
  wrap:     WRAP
  unwrap:   UNWRAP
  children: CHILDREN               ; optional, default #t
  locking:  LOCK-EXPR              ; optional
  ;; Extra slots are optional
  (SLOT SLOT-GETTER SLOT-SETTER)   ; SLOT-SETTER is optional
  ...)

ARMOR-NAME is the record type name to define, using define-record-type.

PRED is the procedure name to define as the type predicate, which returns #t if passed an instance of this armor type, or #f if passed anything else.

WRAP is the procedure name to define as the armor wrapper, which returns a new armor instance that wraps some bare data.

UNWRAP is the procedure name to define as the armor unwrapper, which returns a pointer or locative to the bare data, or #f if it is null.

CHILDREN is a boolean that specifies the default value of whether instances of this armor type track their children, so that the children can be nullified when the instance is freed or nullified. See armor-tracks-children?.

LOCK-EXPR can be either (make-armor-lock) to make instances of this armor type thread safe by default, or #f to make the type not thread-safe by default. See "When should I enable armor thread safety?" in the Getting Started guide for more information. You can also use armor-lock-set! to enable or disable thread safety for a single armor instance.

Each SLOT is an optional extra slot in the record type, which you can use for your own purposes, for example to hold metadata. An extra slot can hold any Scheme object.

Hint If you are defining armor for a C array, and you want to use define-array-allocators, it is recommended that the first extra slot is used to hold the array length. The slot name does not matter, only that it is the first.

Each SLOT-GETTER and SLOT-SETTER are the procedure names to define as the slot getter and setter. If SLOT-SETTER is omitted, no setter procedure will be defined, and that slot will be read-only (it can only be set when the armor is created with WRAP). If SLOT-SETTER is the form (setter SLOT-GETTER), no setter procedure will be defined, but that slot will be settable using (set! (SLOT-GETTER armor) value). If you want to have a setter procedure and also make the getter settable with set!, use (set! (setter SLOT-GETTER) SLOT-SETTER) after defining the armor type (see example).

If you define any extra slots, you may also want to provide SLOT-DEFAULTs if you use define-struct-allocators or define-array-allocators.

Warning The record type will also contain one or more private slots which are used internally by Jiffi. The number and order of slots may change. Only use the slot getters and setters to access the slots. Do not rely on the slot layout staying the same!

Examples:

;; Armor for a C struct/union
(define-armor-type event
  pred: event?
  wrap: wrap-event
  unwrap: unwrap-event
  (extra1  event-extra1  (setter event-extra1))
  (extra2  event-extra2  event-extra2-set!))

;; Also make extra2 settable with (set! (event-extra2 armor) value)
(set! (setter event-extra2) event-extra2-set!)

;; Armor for a C array
(define-armor-type event-array
  pred: event-array?
  wrap: wrap-event-array
  unwrap: unwrap-event-array
  locking: (make-armor-lock)
  (length  event-array-length) ; first extra slot holds the length
  (extra1  event-array-extra1  (setter event-array-extra1)))

Armor wrapper

An armor wrapper procedure defined with define-armor-type has one of the following interfaces, depending on whether extra slots are defined:

(WRAP data) → fresh armor instance
(WRAP data #!optional (SLOT (void)) ...) → fresh armor instance

A wrapper procedure creates an armor instance that wraps a pointer, locative, blob, or #f (which CHICKEN treats as a null pointer). A wrapper procedure is also suitable to pass as RETCONVERT to define-foreign-type.

Each SLOT is an extra slot name passed to define-armor-type. If no extra slots were defined, the wrapper does not accept any optional arguments.

Examples:

;; Manually convert arguments and wrap the returned pointer:

(define-binding FOO_CreateKeyEvent
  return: (nonnull-c-pointer "FOO_Event")
  args: ((int code)
         (int mods)))

(define (create-key-event code mods)
  (wrap-key-event
   (FOO_CreateKeyEvent
    (keycode->int key)
    (pack-keymods mods))))

;; Or define-foreign-type to do it automatically:

(define-foreign-type FOO_KeyEvent*
  (nonnull-c-pointer "FOO_Event")
  unwrap-key-event
  wrap-key-event)

(define-foreign-type FOO_KeyCode
  int
  keycode->int
  int->keycode)

(define-foreign-type FOO_KeyMods
  int
  pack-keymods
  unpack-keymods)

(define-binding (create-key-event FOO_CreateKeyEvent)
  return: FOO_KeyEvent*
  args: ((FOO_KeyCode code)
         (FOO_KeyMods mods)))

Armor unwrapper

An armor unwrapper procedure defined with define-armor-type has the following interface:

(UNWRAP armor #!optional name) → pointer, locative, or #f

Returns a pointer or locative to the bare data inside armor. If armor is null, returns #f (which CHICKEN treats as a null pointer). The return value can be passed to a function binding that accepts a pointer argument. An unwrapper procedure is also suitable to pass as ARGCONVERT to define-foreign-type.

armor must be an instance of the correct armor type, or a pointer, locative, blob, or #f. The reason the unwrapper is generic in this way, is to allow users to pass either an armor or bare data to function bindings.

name is the procedure name to show in error messages if the unwrapper is passed the wrong type. For example, it can be a symbol with the name of the procedure that is calling the unwrapper, to make it easier for users to debug their code. If name is omitted or #f, the error message will not include a procedure name.

Returns a pointer if armor wraps a pointer. If armor's pointer is tagged, the returned pointer will have the same tag. Returns a locative if armor wraps a locative or a blob. Returns #f if armor wraps #f.

Examples:

;; Manually unwrap argument and convert the return value:

(define-binding FOO_BlackboxPower
  return: int
  args: ((c-pointer bb)))

(define (blackbox-power bb)
  (int->power-level
   (FOO_BlackboxPower
    (unwrap-blackbox bb 'blackbox-power))))

;; Or define-foreign-type to do it automatically:

(define-foreign-type FOO_PowerLevel
  int
  power-level->int
  int->power-level)

;; FOO_Blackbox is an opaque pointer type.
(define-foreign-type FOO_Blackbox
  c-pointer
  unwrap-blackbox
  wrap-blackbox)

(define-binding (blackbox-power FOO_BlackboxPower)
  return: FOO_PowerLevel
  args: ((FOO_Blackbox bb)))

armor-printer

[syntax] (armor-printer ...) → procedure

Expands into an anonymous procedure which can be used to print an armor instance as a string, to make debugging easier.

On CHICKEN 5, you can pass the anonymous procedure to set-record-printer!. If you want to support both CHICKEN 4 and CHICKEN 5, it is simpler to use define-armor-printer instead of armor-printer.

Usage:

(armor-printer ARMOR-NAME
  show-address: SHOW-ADDRESS  ; optional
  (LABEL GETTER)              ; optional
  ...)

Record printers are used to output the record instance as a string, for example in REPL results, error messages, or when included in strings. The main purpose of an armor record printer is to provide information to make debugging easier.

Printers created with this macro use the following style:

#<ARMOR-NAME address LABEL: value ...>

If the armor is null, it is printed like:

#<ARMOR-NAME NULL>

ARMOR-NAME is the record type name that was passed to define-armor-type.

If SHOW-ADDRESS is #t, the armor data's memory address is shown in the output, in the format 0x12345678. If SHOW-ADDRESS is #f or the keyword clause is omitted, the address is not shown. (This option does not affect whether NULL is shown when the armor is null. There is currently no option to disable that.)

LABEL can be a non-quoted symbol or string containing the label to show for a field value. For example, it might be the name of a struct field. Or, if LABEL is #f, the field value will be printed with no label.

GETTER is an existing procedure that returns a value when passed the record instance. For example, it could be a field getter defined with define-struct-accessors or struct-getter.

Example:

;; CHICKEN 5 only
(set-record-printer! key-event
  (armor-printer key-event
    show-address: #f
    (#f key-event-code)
    (mods key-event-mods)))

(define ev (create-key-event 'a '(lctrl)))
(printf "~A" ev)    ; #<key-event a mods: (lctrl)>

(free-key-event! ev)
(printf "~A" ev)    ; #<key-event NULL>

define-armor-printer

[syntax] (define-armor-printer ...)

Creates a record printer procedure using armor-printer, and sets it as the record printer for the specified record type. If you do not need to support CHICKEN 4, you may prefer to use armor-printer instead.

Usage:

(define-armor-printer ARMOR-NAME
  show-address: SHOW-ADDRESS  ; optional
  (LABEL GETTER)              ; optional
  ...)

All arguments have the same meaning as with armor-printer.

On CHICKEN 4, this is based on define-record-printer. On CHICKEN 5, it is based on set-record-printer!

Example:

(define-armor-printer key-event
  show-address: #f
  (#f key-event-code)
  (mods key-event-mods))

(define ev (create-key-event 'a '(lctrl)))
(printf "~A" ev)    ; #<key-event a mods: (lctrl)>

(free-key-event! ev)
(printf "~A" ev)    ; #<key-event NULL>

Generic armor operations

These procedures and macros work with instances of any type defined with define-armor-type.

armor?

[procedure] (armor? obj) → boolean

Returns #t if obj is an instance of any type defined with define-armor-type, or #f if obj is anything else.

armor-address

[procedure] (armor-address obj) → integer

Returns the memory address of the bare data that obj refers to. obj can be an instance of a type defined with define-armor-type, or a pointer, locative, blob, or #f (which is treated as a null pointer, address 0).

Warning The memory address of locatives, blobs, and armors wrapping locatives or blobs, may change during garbage collection! If you need the memory address of those things to never change, you can use object-evict to evict the blob or the locative's object before wrapping it in armor.

armor-eq?

[procedure] (armor-eq? obj1 obj2) → boolean

Returns #t if both objects refer to the same memory address. Same as (= (armor-address obj1) (armor-address obj2)).

Each object can be an instance of any type defined with define-armor-type, or a pointer, locative, blob, or #f (which is treated as a null pointer, address 0).

armor-null?

[procedure] (armor-null? obj) → boolean

Returns #t if obj refers to memory address 0. Equivalent to (zero? (armor-address obj)).

obj can be an instance of a type defined with define-armor-type, or a pointer, locative, blob, or #f (which is treated as a null pointer, address 0).

nullify-armor!

[procedure] (nullify-armor! armor) → armor

Sets armor's data to #f (which is treated as a null pointer) and returns armor. armor must be an instance of any type defined with define-armor-type.

This procedure also recursively nullifies armor's children (if it has any), unless armor does not track its children.

Struct freers and array freers will automatically call this procedure for you. You should call this procedure manually to nullify an armor when its data becomes unusable for other reasons, for example when calling a destructor C function.

(define-binding FOO_DestroyBlackbox
  args: ((c-pointer bb)))

(define (destroy-blackbox! bb)
  ;; FOO_DestroyBlackbox frees bb's data, but if bb is an armor it
  ;; will still wrap a pointer to the old address, which could cause
  ;; a memory access violation if bb is used again later.

  ;; Unwrap bb to get its pointer before it is nullified.
  (let ((ptr (unwrap-blackbox bb)))
    ;; Nullify bb if it is an armor (rather than bare data)
    (when (armor? bb)
      (nullify-armor! bb))
    ;; Pass the original pointer.
    (FOO_DestroyBlackbox ptr)))

Armor parents and children

Jiffi supports creating parent-child relationships between armors. This helps ensure proper cleanup and prevent certain kinds of memory-related bugs. See "When should an armor be a child of another armor?" in the Getting Started guide for more information.

Array accessors that return an armor automatically create a parent-child relationship. For other cases, you can create the relationship manually with armor-parent-set!.

If you are using multiple SFRI-18 threads, you may need to enable thread safety on some parent armors.

armor-parent
[procedure] (armor-parent armor) → armor or #f

If armor is the child of another armor, returns the parent armor. Otherwise, returns #f. armor must be an instance of any type defined with define-armor-type.

An armor is the "child" of another armor if it wraps part of the parent armor's bare data. For example, an armor returned by an array accessor is a child of the array armor, because it wraps part of the array's bare data.

See also armor-parent-set!.

armor-parent-set!
[procedure] (armor-parent-set! child parent) → child

Performs internal bookkeeping to create a parent-child relationship between child and parent. Modifies both child and parent. Returns child.

child and parent must each be an instance of any type defined with define-armor-type. They can be the same type or different types.

Warning Calling this procedure from multiple threads at the same time with the same parent may cause the software to crash later or have security vulnerabilities, unless thread safety is enabled on the parent armor.

You do not need to call this procedure for armors returned by array accessors, because they call this procedure automatically. You only need to call this procedure manually if you use an armor wrapper to wrap part of the parent's bare data.

For example, if you have an array of unions, you might want to wrap array items in different armor types depending on what data type they are. See the example below for how to do this using an array item pointer getter.

Creating the parent-child relationship helps ensure proper cleanup and prevent certain kinds of memory-related bugs. See "When should an armor be a child of another armor?" in the Getting Started guide for more information.

Example:

;; Return an event from the array, wrapped in either a key-event or
;; sensor-event instance, depending on what type of event it is.
(define (event-array-ref/typed array i)
  ;; Use the array item pointer getter to get a bare pointer/locative.
  (let* ((ptr (event-array-ref* array i))
         ;; Wrap ptr in the correct armor type.
         (event (case (event-type ptr)
                   ((key) (wrap-key-event ptr))
                   ((sensor) (wrap-sensor-event ptr)))))
    ;; If array is wrapped in armor, set event's parent and return
    ;; event. Otherwise, just return event.
    (if (armor? array)
        (armor-parent-set! event array)
        event)))
armor-tracks-children?
[procedure] (armor-tracks-children? armor) → boolean
[setter] (armor-tracks-children-set! armor enabled)
[setter] (set! (armor-tracks-children? armor) enabled)

Armor instances can optionally perform bookkeeping to keep track of their children (see armor-parent) so that the children can be nullified when the armor instance is freed or nullified. This helps protect against use-after-free bugs. See "When should an armor be a child of another armor?" in the Getting Started guide for more information.

armor-tracks-children? returns #t if armor performs bookkeeping to track its children, or #f if it does not. You can use armor-tracks-children-set! to enable or disable this behavior for armor. If you disable bookkeeping when armor already has children, it will forget about those children.

Hint You can pass the children: keyword argument to define-armor-type to enable or disable bookkeeping by default for all instances of that armor type.

Disabling bookkeeping can improve performance for armors that will have many children, but you must be careful not to use the children after armor has been freed. If armor will never have children, there is no reason to disable bookkeeping.

Armor thread safety

The procedures and macros in this section can be used to make armors safe to use from multiple SRFI-18 threads at the same time. See "When should I enable armor thread safety?" in the Getting Started guide for more information.

make-armor-lock
[syntax] (make-armor-lock) → new armor lock

Creates an armor lock which uses a SRFI-18 mutex to make an armor thread-safe. You must import srfi-18 in the module that calls this macro.

You can use armor-lock-set! to assign the lock to a single armor instance. You can pass locking: (make-armor-lock) to define-armor-type, to create and assign a new lock to every instance of the armor type automatically.

Warning The implementation details of armor locks are unspecified, and may change in future versions of Jiffi. You should not do anything with an armor lock except assign it to an armor as described above.
armor-has-lock?
[procedure] (armor-has-lock? armor) → boolean

Returns #t if armor has a lock to make it thread-safe, or #f if it does not. See armor-lock-set!.

armor-lock-set!
[setter] (armor-lock-set! armor lock)

Set armor's lock to lock, which must either be an armor lock created with make-armor-lock (to make the armor thread-safe), or #f (to make the armor not thread-safe).

Structs and Unions

The macros in this section allow you to create, read, and modify C structs and C unions. Even though the macro names only say "struct", they work with C unions too.

These macros are designed to be used in combination with an armor type. But, it is possible to use them with your own custom record type, if you provide a wrapper procedure or unwrapper procedure with the correct interface. Also, many of the procedures defined by these macros work with bare data, so it is not strictly necessary to use a record type at all.

define-struct-allocators

[syntax] (define-struct-allocators ...)

Defines various procedures to allocate or free memory for a struct/union type defined with define-armor-type. You can omit any procedures that you don't need. Also generates type declarations for the procedures that are defined.

See "Which allocator should I use?" in the Getting Started guide.

Usage:

(define-struct-allocators
  (ARMOR-NAME "STRUCT_NAME" PRED WRAP)
  free:       FREE                 ; optional
  alloc:      ALLOC                ; optional
  alloc/blob: ALLOC/BLOB           ; optional
  make:       MAKE                 ; optional
  make/af:    MAKE/AUTOFREE        ; optional
  make/blob:  MAKE/BLOB            ; optional
  defaults:   (SLOT-DEFAULT ...)   ; optional

ARMOR-NAME is the name of the record type that is returned by WRAP, such as the record name that was passed to define-armor-type.

"STRUCT_NAME" is a string containing the name of the C struct/union, exactly as it appears in C.

PRED is an existing type predicate procedure for the armor type, such as a procedure defined with define-armor-type.

WRAP is an existing procedures that wraps bare data in an armor instance, such as an armor wrapper procedure defined with define-armor-type.

FREE is the procedure name to define as a struct freer. If FREE is #f or the keyword clause is omitted, the procedure will not be defined.

ALLOC and ALLOC/BLOB are the procedure names to define as struct memory allocators. If any is #f or its keyword clause is omitted, that procedure will not be defined.

MAKE, MAKE/AUTOFREE, and MAKE/BLOB are the procedure names to define as struct makers. If any is #f or its keyword clause is omitted, that procedure will not be defined.

Each SLOT-DEFAULT is the default value of each additional SLOT defined with define-armor-type. MAKE, MAKE/AUTOFREE, and MAKE/BLOB will pass the SLOT-DEFAULTs as extra arguments to WRAP.

Example:

(define-struct-allocators
  (event "FOO_Event" event? wrap-event)
  free:       free-event!
  alloc:      alloc-event
  alloc/blob: alloc-event/blob
  make:       make-event
  make/af:    make-event/autofree
  make/blob:  make-event/blob
  defaults:   ("e1" 'e2))     ; defaults for `extra1' and `extra2'

Struct freer

A struct freer procedure defined with define-struct-allocators has the following interface:

(FREE struct) → struct

Frees struct's bare data (if appropriate), and nullifies struct (and possibly its children). Returns struct.

struct must be an instance of the armor type that this freer was defined for.

If struct wraps a non-null pointer, the pointer is freed using free. If struct wraps a blob or locative, this procedure only nullifies struct, which may cause the blob or locative to be garbage collected if there are no other references to it. If struct is already null, this procedure has no effect, so it is safe to call on a struct that has already been freed, or that will be automatically freed later.

But, if struct has a parent, this procedure only nullifies struct. It does not free the bare data, because the bare data is owned by the parent.

Struct memory allocators

Struct memory allocator procedures defined with define-struct-allocators have the following interfaces:

(ALLOC) → tagged pointer to allocated memory
(ALLOC/BLOB) → new blob

Allocates memory of the correct size to hold an instance of the C struct/union. The memory is initialized to zero.

ALLOC allocates a region of static memory using allocate, tags the pointer with 'ARMOR-NAME, and returns the tagged pointer. To avoid memory leaks, you must free the pointer with free when you are done with it.

ALLOC/BLOB creates a blob using make-blob. The memory will be automatically freed when the blob is garbage collected.

See "Warning about bare data" in the Getting Started guide. Struct makers are much safer, although a little less efficient.

Struct makers

Struct maker procedures defined with define-struct-allocators have the following interfaces:

(MAKE) → new armor
(MAKE/AUTOFREE) → new armor
(MAKE/BLOB) → new armor

Allocates memory of the correct size to hold an instance of the C struct/union, then wraps it in an armor instance using WRAP. The memory is initialized to zero. The SLOT-DEFAULTs (if any) will be passed as extra arguments to WRAP.

MAKE allocates a region of static memory using allocate, tags the pointer with 'ARMOR-NAME, then wraps the tagged pointer. To avoid memory leaks, you must manually free the record instance with this type's freer when you are done with it.

MAKE/AUTOFREE is like MAKE, except it also sets the struct's finalizer to automatically free the memory when the armor instance is garbage collected.

MAKE/BLOB creates a blob using make-blob, then wraps it. The blob will be garbage collected when the record instance is garbage collected, if there are no other references to the blob.

struct-copier

[syntax] (struct-copier ...) → procedure

Expands to an anonymous procedure that performs a shallow copy of the memory from the source struct/union, overwriting the destination struct/union. Returns the destination struct/union after copying.

Usage:

(struct-copier
  ("STRUCT_NAME" UNWRAP)
  name: NAME)  ; optional

"STRUCT_NAME" is a string containing the name of the C struct/union, exactly as it appears in C.

UNWRAP is an existing armor unwrapper procedure, such as a procedure defined with define-armor-type.

NAME is an expression that evaluates to the procedure name (usually a symbol) to show in error messages if either struct argument cannot be unwrapped. If NAME is omitted or #f, error messages will not include a procedure name.

The procedure will behave similar to this pseudo-code:

(lambda (src dest)
  (copy-bytes! (UNWRAP src NAME)
               (UNWRAP dest NAME)
               (foreign-type-size STRUCT_NAME))
  dest)
Hint The procedure does not copy the values of extra slots you may have defined with define-armor-type.
Warning The procedure only performs a shallow copy of the C struct/union, byte-for-byte. This is safe for C structs/unions that only hold direct values like numbers or fixed-size arrays. But, if the C struct/union has a field that holds a pointer, that field of both the source and destination structs will hold a pointer to the same address, which can cause bugs if you are not careful.

src and dest can each be an armor, pointer, locative, or blob. It is okay to be different, for example if src is a blob and dest is a pointer. See "Warning about bare data" in the Getting Started guide. The procedure signals an exception if either src or dest are null.

Example:

;; Destructive copier
(define copy-event!
  (struct-copier
   ("FOO_Event" unwrap-event)
   name: 'copy-event!))

;; Non-destructive copier based on destructive copier
(define (copy-event ev)
  (copy-event! ev (make-event/blob)))

;; Or using struct-copier directly inside a procedure
(define (copy-event ev)
  ((struct-copier
    ("FOO_Event" unwrap-event)
    name: 'copy-event)
   ev (make-event/blob)))

define-struct-accessors

[syntax] (define-struct-accessors ...)

Defines getters and/or setters for one or more fields of a C struct/union. Also generates type declarations for the getters and setters.

Usage:

(define-struct-accessors
  (ARMOR-NAME "STRUCT_NAME" PRED UNWRAP)
  ("FIELD_NAME"
   type:   (SCHEME-TYPE FOREIGN-TYPE)   ; or `type: FOREIGN-TYPE`
   getter: GETTER                       ; optional
   g-conv: G-CONV                       ; optional
   setter: SETTER                       ; optional
   s-conv: S-CONV)                      ; optional
  ...)

ARMOR-NAME is the name of the record type that is unwrapped by UNWRAP, such as the record name that was passed to define-armor-type.

"STRUCT_NAME" is a string containing the name of the C struct/union, exactly as it appears in C.

PRED is an existing type predicate for the armor type, such as a procedure defined with define-armor-type.

UNWRAP is an existing armor unwrapper procedure, such as a procedure defined with define-armor-type.

"FIELD_NAME" is a string containing the name of the field, exactly as it appears in C. You can access nested data by using "." or "->" in the field name. For example, "foo.bar" would access the foo field of the struct/union, then the bar field of foo. If the foo field holds a pointer, you would write "foo->bar" instead.

You can define multiple accessors with the same "FIELD_NAME", as long as the GETTER and SETTER names are different. For example, you might want to define a getter that returns the field as an integer, and another getter that uses an enum converter as G-CONV to convert the field into a symbol.

SCHEME-TYPE is a Scheme type specifier, which is used as the return type of the getter (after conversion) and as the argument type of the setter (before conversion). FOREIGN-TYPE is a foreign type specifier for the struct field. If SCHEME-TYPE and FOREIGN-TYPE are the same, you can write type: FOREIGN-TYPE.

GETTER and SETTER are procedure names to define as the getter and setter procedures. Alternatively, if SETTER is the form (setter GETTER), the field will be settable using (set! (GETTER struct) value). The procedures are created using struct-getter and struct-setter. You can omit either keyword clause if you don't need a getter or setter.

G-CONV is an existing procedure used by GETTER to convert the field value before returning it. For example, this could be a int->symbol enum converter defined with define-enum-group, an enum unpacker defined with define-enum-unpacker, or an armor wrapper defined with define-armor-type. The procedure must accept one argument, the value to convert. If G-CONV is #f or the keyword clause is omitted, the getter will not perform any conversion.

S-CONV is an existing procedure used by SETTER to validate and/or convert the argument before setting the field value. For example, this could be a symbol->int enum converter defined with define-enum-group, an enum packer defined with define-enum-packer, or an armor unwrapper defined with define-armor-type. You can perform validation by signaling an exception if the argument is invalid. The procedure must accept one argument, the value to convert. If S-CONV is #f or the keyword clause is omitted, the setter will not perform any conversion.

Example:

;; C struct and union definitions
(foreign-declare "
typedef struct {
  int type;
  FOO_KeyCode code;
  FOO_KeyMod mods;
} FOO_KeyEvent;

/* Union of two structs. */
typedef union {
  int type;
  FOO_KeyEvent key;
  FOO_SensorEvent sensor;
} FOO_Event;
")

(define-struct-accessors
  (key-event "FOO_Event" key-event? unwrap-key-event)

  ;; Access the "key" union member, then the "code" struct field
  ("key.code"
   type:   (fixnum int)
   getter: key-event-code-int
   setter: key-event-code-int-set!)

  ;; With enum converters
  ("key.code"
   type:   (symbol int)
   getter: key-event-code
   g-conv: int->keycode
   setter: key-event-code-set!
   s-conv: keycode->int))

;; Also make code settable with (set! (key-event-code struct) value)
(set! (setter key-event-code) key-event-code-set!)

struct-getter

[syntax] (struct-getter ...) → procedure

Expands to an anonymous procedure that gets the value of a struct/union field.

Usage:

(struct-getter
 (ARMOR-NAME "STRUCT_NAME" PRED UNWRAP)
 FIELD-NAME
 type: FOREIGN-TYPE
 conv: G-CONV            ; optional
 name: NAME)             ; optional

NAME is an expression that evaluates to the procedure name (usually a symbol) to show in error messages if the struct cannot be unwrapped. If NAME is omitted or #f, error messages will not include a procedure name.

See define-struct-accessors for the meaning of the other macro arguments.

The procedure will behave similar to this pseudo-code:

(lambda (struct)
  (let ((ptr (UNWRAP struct NAME)))
    (if G-CONV
        (G-CONV (get-field ptr FIELD-NAME))
        (get-field ptr FIELD-NAME))))

Example:

(define event-type-getter
  (struct-getter
   (event "FOO_Event" event? unwrap-event)
   "type"
   type: int
   conv: int->event-type
   name: 'event-type-getter))

struct-setter

[syntax] (struct-setter ...) → procedure

Expands to an anonymous procedure that sets a struct field value.

Usage:

(struct-setter
 (ARMOR-NAME "STRUCT_NAME" PRED UNWRAP)
 FIELD-NAME
 type: FOREIGN-TYPE
 conv: S-CONV            ; optional
 name: NAME)             ; optional

NAME is an expression that evaluates to the procedure name (usually a symbol) to show in error messages if the struct cannot be unwrapped. If NAME is omitted or #f, error messages will not include a procedure name.

See define-struct-accessors for the meaning of the other macro arguments.

The procedure will behave similar to this pseudo-code:

(lambda (struct value)
  (let ((ptr (UNWRAP struct NAME)))
    (if S-CONV
        (set-field ptr FIELD-NAME (S-CONV value))
        (set-field ptr FIELD-NAME value))))

Example:

(set! (setter key-event-code)
      (struct-setter
       (key-event "FOO_Event" key-event? unwrap-key-event)
       "key.code"
       type: int
       conv: keycode->int
       name: 'key-event-code))

Arrays of Structs and Unions

The macros in this section allow you to create, read, and modify C arrays of structs or unions.

These macros are designed to be used in combination with an armor type. But, it is possible to use the allocators and some of the array accessors with your own custom record type, if you provide a wrapper procedure or unwrapper procedure with the correct interface. Also, some of the allocators work with bare data, so it is not strictly necessary to use a record type at all.

define-array-allocators

[syntax] (define-array-allocators ...)

Defines various procedures to manage memory for an armor type that will hold a C array of structs or unions. You can omit any procedures that you don't need. Also generates type declarations for the procedures that are defined.

See "Which allocator should I use?" in the Getting Started guide.

Usage:

(define-array-allocators
  (ARMOR-NAME "STRUCT_NAME" PRED WRAP)
  free:       FREE                 ; optional
  alloc:      ALLOC                ; optional
  alloc/blob: ALLOC/BLOB           ; optional
  make:       MAKE                 ; optional
  make/af:    MAKE/AUTOFREE        ; optional
  make/blob:  MAKE/BLOB            ; optional
  defaults:   (SLOT-DEFAULT ...)   ; optional

This is very similar to define-struct-allocators, except that the "alloc" and "make" procedures take a length argument for the number of items in the array.

"STRUCT_NAME" is a string containing the name of the C struct/union (not the array type), exactly as it appears in C. For example, for an array of FOO_Event structs, just write "FOO_Event".

FREE will be defined as an array freer, ALLOC and ALLOC/BLOB will be defined as array memory allocators, and MAKE, MAKE/AUTOFREE, and MAKE/BLOB will be defined as array makers.

The SLOT-DEFAULTs are the defaults for the slots after the first. They should not include a default for first slot, which is recommended to hold the array length. The "make" procedures will call (WRAP data length SLOT-DEFAULT ...).

Example:

(define-array-allocators
  (event "FOO_Event" event? wrap-event)
  free:       free-event!
  alloc:      alloc-event
  alloc/blob: alloc-event/blob
  make:       make-event
  make/af:    make-event/autofree
  make/blob:  make-event/blob
  defaults:   ("e1" 'e2))     ; defaults for `extra1' and `extra2'

Array armor length slot

If you use define-armor-type to define armor that wraps a C array of structs or unions, it is recommended that the first extra slot be reserved for holding the array length. This is because "make" procedures defined with define-array-allocators will pass the array length as the second argument to WRAP. If the first extra slot is the length slot, you don't have to do anything special to make that work.

If for some reason you cannot use the first extra slot to hold the length, you will need to create an "adapter" procedure to pass to define-array-allocators instead of WRAP. For example, if length is the third extra slot:

(define-armor-type thing-array
 pred: thing-array?
 wrap: wrap-thing-array
 unwrap: unwrap-thing-array
 ;; length is not the first extra slot
 (slot1 thing-array-slot1 (setter thing-array-slot1))
 (slot2 thing-array-slot2 (setter thing-array-slot2))
 (length thing-array-length))

;; Adapter procedure that reorders the slot arguments
(define (thing-array-adapter data #!optional length slot1 slot2)
  (wrap-thing-array data slot1 slot2 length))

(define-array-allocators
  ;; Pass thing-array-adapter instead of wrap-thing-array
  (thing-array "Thing" thing-array? thing-array-adapter)
  ...
  defaults: ("slot1 default" "slot2 default"))

Array freer

An array freer procedure defined with define-array-allocators has the following interface:

(FREE array) → array

Array freers behave the same as struct freers.

Array memory allocators

Array memory allocator procedures defined with define-array-allocators have the following interfaces:

(ALLOC length) → tagged pointer to allocated memory
(ALLOC/BLOB length) → new blob

Allocates memory of the correct size for a C array containing length instances of the C struct/union. The memory is initialized to zero.

See "Warning about bare data" in the Getting Started guide. Array makers are much safer, although a little less efficient.

ALLOC allocates a region of static memory using allocate, tags the pointer with 'ARMOR-NAME, and returns the tagged pointer. To avoid memory leaks, you must free the pointer with free when you are done with it.

ALLOC/BLOB creates a blob using make-blob. The memory will be automatically freed when the blob is garbage collected.

Array makers

Array maker procedures defined with define-array-allocators have the following interfaces:

(MAKE length) → new array record
(MAKE/AUTOFREE length) → new array record
(MAKE/BLOB length) → new array record

Allocates memory of the correct size for a C array containing length instances of the struct/union, then wraps it in an armor instance using WRAP. The memory is initialized to zero. The SLOT-DEFAULTS (if any) will be passed as extra arguments to WRAP.

MAKE allocates a region of static memory using allocate, tags the pointer with 'ARMOR-NAME, then wraps the tagged pointer. To avoid memory leaks, you must manually free the record instance with this type's freer when you are done with it.

MAKE/AUTOFREE is like MAKE, except it also sets the array's finalizer to automatically free the array memory when the record is garbage collected.

MAKE/BLOB creates a blob using make-blob, then wraps it. The blob will be garbage collected when the record instance is garbage collected, if there are no other references to the blob.

define-array-accessors

[syntax] (define-array-accessors ...)

Defines various procedures to get or set items of a C array of structs/unions. You can omit any procedures that you don't need. Also generates type declarations for the defined procedures.

Usage:

(define-array-accessors
  (ARMOR-NAME "STRUCT_NAME" PRED UNWRAP LENGTH)
  (ITEM-ARMOR-NAME ITEM-PRED ITEM-WRAP ITEM-UNWRAP)
  ref:       REF         ; optional
  set:       SET         ; optional
  map:       MAP         ; optional
  for-each:  FOR-EACH    ; optional
  ref*:      REF*        ; optional
  map*:      MAP*        ; optional
  for-each*: FOR-EACH*)  ; optional

ARMOR-NAME is the record type name for the array, such as the record name passed to define-armor-type.

"STRUCT_NAME" is a string containing the name of the C struct/union (not the array type), exactly as it appears in C. For example, for an array of FOO_Event structs, just write "FOO_Event".

PRED is an existing type predicate for the array record type, such as a procedure defined with define-armor-type.

UNWRAP is an existing armor unwrapper procedure, such as a procedure defined with define-armor-type.

LENGTH is an existing procedure that returns the array length, such as an extra slot getter defined with define-armor-type.

ITEM-ARMOR-NAME is the record type name that was passed to define-armor-type for the item type (not the array type).

ITEM-PRED, ITEM-WRAP, and ITEM-UNWRAP are existing procedures for the item type (not the array type), such as the procedures defined with define-armor-type. ITEM-WRAP must return an instance of a type defined with define-armor-type.

REF is the procedure name to define as an array item getter, which returns an armor instance wrapping the bare data of an item in the array. If REF is #f or the keyword clause is omitted, the procedure will not be defined.

SET is the procedure name to define as an array item setter, which overwrites the memory for an item in the array. If SET is #f or the keyword clause is omitted, the procedure will not be defined and REF will not be settable. If SET is the form (setter REF), no setter procedure will be defined, but the array will be settable using (set! (REF array i) struct). If you want to have a setter procedure and also make the array settable with set!, use (set! (setter REF) SET) after defining the array accessors.

MAP is the procedure name to define as an array item mapper, which repeatedly calls a procedure with each item in one or more arrays, and returns a list of the results. If MAP is #f or the keyword clause is omitted, the procedure will not be defined.

FOR-EACH is the procedure name to define as an array item iterator, which repeatedly calls a procedure with each item in one or more arrays, in a guaranteed order, but does not return anything. If FOR-EACH is #f or the keyword clause is omitted, the procedure will not be defined.

REF* is the procedure name to define as an array item pointer getter, which returns a pointer or locative to an item in an array. If REF* is #f or the keyword clause is omitted, the procedure will not be defined.

MAP* is the procedure name to define as an array item pointer mapper, which repeatedly calls a procedure with a pointer or locative to each item in one or more arrays, and returns a list of the results. If MAP* is #f or the keyword clause is omitted, the procedure will not be defined.

FOR-EACH* is the procedure name to define as an array item pointer iterator, which repeatedly calls a procedure with a pointer or locative to each item in one or more arrays, in a guaranteed order, but does not return anything. If FOR-EACH* is #f or the keyword clause is omitted, the procedure will not be defined.

Example:

(define-array-accessors
  (event-array "FOO_Event" event-array? unwrap-event-array event-array-length)
  (event event? wrap-event unwrap-event)
  ref:       event-array-ref
  set:       (setter event-array-ref)
  map:       event-array-map
  for-each:  event-array-for-each
  ref*:      event-array-ref*
  map*:      event-array-map*
  for-each*: event-array-for-each*)

Array item getter

An array item getter procedure defined with define-array-accessors has the following interface:

(REF array i) → fresh armor instance

Returns an armor instance wrapping the item at index i of the array (starting at index 0). Signals an exception if i is out of bounds.

array must be a non-null instance of the armor type that this procedure was defined for.

The returned item's type is whatever ITEM-WRAP returns. It must be an instance of a type defined with define-armor-type. The item will refer to a location in the array's memory, so modifying the item will modify the array.

A parent-child relationship will be automatically created between the array and the item, using armor-parent-set!. The array will not be garbage collected while the item is using its memory. If you manually free the array with the array freer, the item will be automatically nullified to prevent memory errors (unless you passed children: #f when defining the array's armor type).

It is safe to free the item using a struct freer, but doing so will only call nullify-armor on the item armor, not actually free the memory. The memory is owned by the array, and will only be freed when the array is freed.

Warning Calling this procedure from multiple threads at the same time on the same array may cause the software to crash later or have security vulnerabilities, unless you enable thread safety when defining the armor type.

Array item setter

An array item setter procedure defined with define-array-accessors has one of the following interfaces:

(SET array i item)
(set! (REF array i) item)   ; if SET is the form (setter REF)

Copies the memory from item, overwriting the memory at index i of the array (starting at index 0). Signals an exception if i is out of bounds.

Warning Any existing armor instances, pointers, or locatives that refer to the array memory at index i will also be affected by the changes.

array must be a non-null instance of the armor type that this procedure was defined for.

item must be an armor instance, pointer, blob, or locative referring to the new data. It must be a valid argument to ITEM-UNWRAP. The memory from item is shallow-copied to overwrite the array at index i, as with struct-copier. It is safe for item to be an item from array. It is safe for item itself to refer to the item at index i (in other words, it is safe to overwrite itself).

See "Warning about bare data" in the Getting Started guide.

Array item mapper

An array item mapper procedure defined with define-array-accessors has the following interface:

(MAP proc array_1 ... array_n) → list

Calls proc with each array index and the item(s) at that index of the array(s), and returns a list containing each result of proc, in order by array index. Unlike an array item iterator, the temporal order in which proc is applied to the items of the array(s) is unspecified, so you should not depend on side effects happening in order.

proc must be a procedure with the interface (proc i item_1 ... item_n), where i is the array index, and each item is the item from each array at index i. proc should return a single value.

Each item will behave like it was returned from an array item getter. In particular, each item will refer to a location in the corresponding array's memory, so modifying the item will modify that array. Also, a parent-child relationship will be created between the array and the item.

Unlike array item iterators, you can use the items passed to proc in any way you want, and keep references to them however long you want.

Each array must be a non-null instance of the armor type that this mapper procedure was defined for. If the arrays are different lengths, mapping will end when the smallest array is exhausted.

Warning Calling this procedure from multiple threads at the same time with the same array may cause the software to crash later or have security vulnerabilities, unless you enable thread safety when defining the array's armor type.

Array item iterator

An array item iterator procedure defined with define-array-accessors has the following interface:

(FOR-EACH proc array_1 ... array_n)

Calls proc with each array index and the item(s) at that index of the array(s). The iterator does not return anything. Unlike an array item mapper, the iterator is guaranteed to apply proc to the items of the array(s) in order from the first item(s) to the last, so you can depend on side effects happening in order.

proc must be a procedure with the interface (proc i item_1 ... item_n), where i is the array index, and each item is the item from each array at index i.

The items will be whatever type ITEM-WRAP returns. It must be an instance of a type defined with define-armor-type.

Each item will refer to a location in the corresponding array's memory, so modifying the item will modify that array.

Warning Unlike array item mappers, you must not use any item after proc finishes executing. For example, do not store the item in a variable outside of proc and then try to use it later (see example). This is because the iterator may re-use the same armor instance(s) each time proc is called, for efficiency.

Each array must be a non-null instance of the armor type that this mapper procedure was defined for. If the arrays are different lengths, iteration will end when the smallest array is exhausted.

Example:

(let ((array1 (make-event-array 20))
      (array2 (make-event-array 3))
      (results '())
      (old-event #f))

  (event-array-for-each
   (lambda (i event1 event2)
     ;; Good. You can modify the items within proc, and the changes
     ;; will be written to the array.
     (set! (event-type event1) 'key)
     (set! (event-type event2) 'sensor)

     ;; Good. You can copy data out of the items and keep it even
     ;; after proc ends.
     (set! results
           (append results
                   (list i (event-type event1) (event-type event2))))

     ;; Bad! Don't keep references to the armors after proc ends!
     (set! old-event event2))
   array1
   array2)

  ;; Iteration stopped when the smallest array was exhausted.
  (print results)   ; (0 key sensor 1 key sensor 2 key sensor)

  ;; The event armors passed to proc are no longer valid!
  (print (event-type old-event)))  ; error

Array item pointer getter

An array item pointer getter procedure defined with define-array-accessors has the following interface:

(REF* array i) → tagged pointer or locative

Returns a pointer or locative referring to the memory of the item at index i of the array (starting at index 0). Signals an exception if i is out of bounds.

array must be a non-null instance of the armor type that this procedure was defined for. If array wraps a pointer, this procedure returns a pointer tagged with 'ITEM-ARMOR-NAME; if array wraps a blob or locative, this procedure returns a locative.

Warning The memory is owned by the array. You must not free the pointer or locative, and you must not use the pointer or locative after the array has been freed. Doing so can cause the software to crash or have security vulnerabilities.

See "Warning about bare data" in the Getting Started guide. Array item getters are much safer, although a little less efficient.

Array item pointer mapper

An array item pointer mapper procedure defined with define-array-accessors has the following interface:

(MAP* proc array_1 ... array_n) → list

Calls proc with each array index and a pointer or locative referring to the memory of the item(s) at that index of the array(s), and returns a list containing each result of proc, in order by array index. Unlike an array item pointer iterator, the temporal order in which proc is applied to the items of the array(s) is unspecified, so you should not depend on side effects happening in order.

proc must be a procedure with the interface (proc i ptr_1 ... ptr_n), where i is the array index, and each ptr is a pointer or locative referring to the item from each array at index i. proc should return a single value.

Each array must be a non-null instance of the armor type that this mapper procedure was defined for. If the arrays are different lengths, mapping will end when the smallest array is exhausted. If an array wraps a pointer, proc will be passed a pointer tagged with 'ITEM-ARMOR-NAME; if an array wraps a blob or locative, proc will be passed a locative.

Warning The memory is owned by the array. You must not free the pointer or locative, and you must not use the pointer or locative after the array has been freed. Doing so can cause the software to crash or have security vulnerabilities.

See "Warning about bare data" in the Getting Started guide. Array item mappers are much safer, although a little less efficient.

Array item pointer iterator

An array item pointer iterator procedure defined with define-array-accessors has the following interface:

(FOR-EACH* proc array_1 ... array_n)

Calls proc with each array index and a pointer or locative referring to the memory of the item(s) at that index of the array(s). The iterator does not return anything. Unlike an array item pointer mapper, the iterator is guaranteed to call proc on the items of the array(s) in order from the first item(s) to the last, so you can depend on side effects happening in order.

proc must be a procedure with the interface (proc i ptr_1 ... ptr_n), where i is the array index, and each ptr is a pointer or locative referring to the item from each array at index i.

Each array must be a non-null instance of the armor type that this mapper procedure was defined for. If the arrays are different lengths, iteration will end when the smallest array is exhausted. If an array wraps a pointer, proc will be passed a pointer tagged with 'ITEM-ARMOR-NAME; if an array wraps a blob or locative, proc will be passed a locative.

Warning The memory is owned by the array. You must not free the pointer or locative, and you must not use the pointer or locative after the array has been freed. Doing so can cause the software to crash or have security vulnerabilities.

See "Warning about bare data" in the Getting Started guide. Array item iterators are much safer, although a little less efficient.