3.6 Function Types
procedure
(_cprocedure input-types output-type [ #:abi abi #:varargs-after varargs-after #:atomic? atomic? #:async-apply async-apply #:lock-name lock-name #:in-original-place? in-original-place? #:blocking? blocking? #:callback-exns? callback-exns? #:save-errno save-errno #:wrapper wrapper #:keep keep]) → any input-types : (list ctype?) output-type : ctype? abi : (or/c #f 'default 'stdcall 'sysv) = #f varargs-after : (or/c #f positive-exact-integer?) = #f atomic? : any/c = #f async-apply : (or/c #f ((-> any/c) . -> . any/c) box?) = #f lock-name : (or/c string? #f) = #f in-original-place? : any/c = #f blocking? : any/c = #f callback-exns? : any/c = #f save-errno : (or/c #f 'posix 'windows) = #f wrapper : (or/c #f (procedure? . -> . procedure?)) = #f keep : (or/c boolean? box? (any/c . -> . any/c)) = #t
The resulting type can be used to reference foreign functions (usually ffi-objs, but any pointer object can be referenced with this type), generating a matching foreign callout object. Such objects are new primitive procedure objects that can be used like any other Racket procedure. As with other pointer types, #f is treated as a NULL function pointer and vice versa.
A type created with _cprocedure can also be used for passing Racket procedures to foreign functions, which will generate a foreign function pointer that calls to the given Racket callback procedure. There are no restrictions on the representation of the Racket procedure; in particular, the procedure can have free variables that refer to bindings in its environment. Callbacks are subject to run-time constraints, however, such as running in atomic mode or not raising exceptions; see more information on callbacks below.
The optional abi keyword argument determines the foreign ABI
that is used. Supplying #f or 'default indicates the
platform-dependent default. The other possible
values—
The optional varargs-after argument indicates whether some function-type arguments should be considered “varargs,” which are argument represented by an ellipsis ... in the C declaration (but by explicit arguments in input-types). A #f value indicates that the C function type does not have varargs. If varargs-after is a number, then arguments after the first varargs-after arguments in input-types are varargs. Note that #f is different from (length input-types) on some platforms; the possibility of varargs for a function may imply a different calling convention even for non-vararg arguments. Note also that a non-#f varargs-after does not mean that you can supply any number of arguments to a callout or receive any number of arguments to a callback using the procedure type; to work with different argument counts and argument types, use _cprocedure (or _fun) separately for each combination.
For callouts to foreign functions with the generated type:
If save-errno is 'posix, then the value of errno is saved (specific to the current thread) immediately after a foreign function callout returns. The saved value is accessible through saved-errno. If save-errno is 'windows, then the value of GetLastError() is saved for later use via saved-errno; the 'windows option is available only on Windows (on other platforms saved-errno will return 0). If save-errno is #f, no error value is saved automatically.
The error-recording support provided by save-errno is needed because the Racket runtime system may otherwise preempt the current Racket thread and itself call functions that set error values.
If wrapper is not #f, it takes the callout that would otherwise be generated and returns a replacement procedure. Thus, wrapper acts a hook to perform various argument manipulations before the true callout is invoked, and it can return different results (for example, grabbing a value stored in an “output” pointer and returning multiple values).
If lock-name is not #f, then a process-wide lock with the given name is held during the foreign call. In a build that supports parallel places, lock-name is registered via scheme_register_process_global, so choose names that are suitably distinct.
If in-original-place? is true, then when a foreign callout procedure with the generated type is called in any Racket place, the procedure is called from the original Racket place. Use this mode for a foreign function that is not thread-safe at the C level, which means that it is not place-safe at the Racket level. Callbacks from place-unsafe code back into Racket at a non-original place typically will not work, since the place of the Racket code may have a different allocator than the original place.
If blocking? is true, then a foreign callout deactivates tracking of the calling OS thread—
to the degree supported by the Racket variant— during the foreign call. The value of blocking? affects only the CS implementation of Racket, where it enable activity such as garbage collection in other OS threads while the callout blocks. Since a garbage collection can happen during the foreign call, objects passed to the foreign call need to be immobile if they’re managed by the garbage collector; in particular, any _ptr arguments should normally specify 'atomic-interior allocation mode. If the blocking callout can invoke any callbacks back to Racket, those callbacks must be constructed with a non-#f value of async-apply, even if they are always applied in the OS thread used to run Racket. If callback-exns? is true, then a foreign callout allows an atomic callback during the foreign call to raise an exception that escapes from the foreign call. From the foreign library’s perspective, the exception escapes via longjmp. Exception escapes are implemented through an exception handler that catches and reraises the exception.
A callback that raises an exception must be an atomic callback in the BC implementation of Racket (and callbacks are always atomic in the CS implementation). Raising an exception is not allowed in a callback that has an async-apply, since the callback will run in an unspecified context. Raising an exception is also not allowed if the callout (that led to the callback) was created with in-original-place? as true and called in a non-original place.
Values that are provided to a callout (i.e., the underlying callout, and not the replacement produced by a wrapper, if any) are always considered reachable by the garbage collector until the called foreign function returns. If the foreign function invokes Racket callbacks, however, beware that values managed by the Racket garbage collector might be moved in memory by the garbage collector.
A callout object is finalized internally. Beware of trying to use a callout object that is reachable only from a finalized object, since the two objects might be finalized in either order.
For callbacks to Racket functions with the generated type:
The keep argument provides control over reachability by the garbage collector of the underlying value that foreign code see as a plain C function. Additional care must be taken in case the foreign code might retain the callback function, in which case the callback value must remain reachable or else the held callback will become invalid. The possible values of keep are as follows:
#t —
the callback stays in memory as long as the converted Racket function is reachable. This mode is the default, as it is fine in most cases. Note that each Racket function can hold onto only one callback value through this mode, so it is not suitable for a function used multiple times as a reatined callback. #f —
the callback value is not held. This mode may be useful for a callback that is only used for the duration of the foreign call; for example, the comparison function argument to the standard C library qsort function is only used while qsort is working, and no additional references to the comparison function are kept. Use this option only in such cases, when no holding is necessary and you want to avoid the extra cost. A box holding #f or any other non-list value —
the callback value is stored in the box, overriding any non-list value that was in the box (making it useful for holding a single callback value). When you know that the callback is no longer needed, you can “release” the callback value by changing the box contents or by allowing the box itself to become unreachable. This mode can be useful if the box is held for a dynamic extent that corresponds to when the callback is needed; for example, you might encapsulate some foreign functionality in a Racket class or a unit, and keep the callback box as a field in new instances or instantiations of the unit. A box holding null (or any list) —
similar to a box holding a non-list value, except that new callback values are consed onto the contents of the box. This mode is therefore useful in cases when a Racket function is used in multiple callbacks (that is, sent to foreign code to hold onto multiple times) and all callbacks should be retained together. A one-argument function —
the function is invoked with the callback value when it is generated. This mode allows you to explicitly manage reachability of the generated callback closure.
If wrapper is not #f, it takes the procedure to be converted into a callback and returns a replacement procedure to be invoked as the callback. Thus, wrapper acts a hook to perform various argument manipulations before a Racket callback function is called, and it can return different results to the foreign caller.
The callback value’s reachability (and its interaction with keep) is based on the original function for the callback, not the result of wrapper.
If atomic? is true or when using the CS implementation of Racket, then when a Racket procedure is given this type and called as a callback from foreign code, then the Racket process is put into atomic mode while evaluating the Racket procedure body.
In atomic mode, other Racket threads do not run, so the Racket code must not call any function that potentially blocks on synchronization with other threads, or else it may lead to deadlock. In addition, the Racket code must not perform any potentially blocking operation (such as I/O), it must not raise an uncaught exception unless called through a callout that supports exception (with #:callback-exns? #t), it must not perform any escaping continuation jumps, and (at least for the BC implementation) its non-tail recursion must be minimal to avoid C-level stack overflow; otherwise, the process may crash or misbehave.
Callbacks are always atomic in the CS implementation of Racket, because Racket threads do not capture C-stack context. Even on the BC implementation of Racket, atomic mode is typically needed for callbacks, because capturing by copying a portion of the C stack is often incompatible with C libraries.
If a callback in atomic mode sends a break to the current thread, then not only is the break delayed as usual for atomic mode, it delivery might be delayed further than return from a foreign call that led to the callback.
If a async-apply is provided as a procedure or box, then a Racket callback procedure with the generated procedure type can be applied in a foreign thread (i.e., an OS-level thread other than the one used to run Racket).
If async-apply is a procedure, the call in the foreign thread is transferred to the OS-level thread that runs Racket, but the Racket-level thread (in the sense of thread) is unspecified; the job of the provided async-apply procedure is to arrange for the callback procedure to be run in a suitable Racket thread.
The given async-apply procedure is applied to a thunk that encapsulates the specific callback invocation, and the foreign OS-level thread blocks until the thunk is called and completes; the thunk must be called exactly once, and the callback invocation must return normally. The given async-apply procedure itself is called in atomic mode (see atomic? above).
If the callback is known to complete quickly, requires no synchronization, and works independent of the Racket thread in which it runs, then it is safe for the given async-apply procedure to apply the thunk directly. Otherwise, the given async-apply procedure must arrange for the thunk to be applied in a suitable Racket thread sometime after the given async-apply procedure itself returns; if the thunk raises an exception or synchronizes within an unsuitable Racket-level thread, it can deadlock or otherwise damage the Racket process.
If async-apply is a box, then the value contained in the box is used as the result of the callback when it is called in a foreign thread; the async-apply value is converted to a foreign value at the time that _cprocedure is called. Using a boxed constant value for async-apply avoids the need to synchronize with the OS-level thread that runs Racket, but it effectively ignores the Racket procedure that is wrapped as callback when the callback is applied in a foreign thread.
Foreign-thread detection to trigger async-apply works only when Racket is compiled with OS-level thread support, which is the default for many platforms. If a callback with an async-apply is called from foreign code in the same OS-level thread that runs Racket, then async-apply is not used.
A callback normally should not escape by raising an exception or invoking a continuation. An atomic callback can potentially raise an exception, but only if it is called during the invocation of a callout created with callback-exns? as true. A non-atomic callback must never raise an exception.
Changed in version 6.3 of package base: Added the #:lock-name argument.
Changed in version 6.12.0.2: Added the #:blocking? argument.
Changed in version 7.9.0.16: Added the #:varargs-after argument.
Changed in version 8.0.0.8: Added the #:callback-exns? argument.
syntax
(_fun fun-option ... maybe-args type-spec ... -> type-spec maybe-wrapper)
fun-option = #:abi abi-expr | #:varargs-after varargs-after-expr | #:save-errno save-errno-expr | #:keep keep-expr | #:atomic? atomic?-expr | #:async-apply async-apply-expr | #:lock-name lock-name-expr | #:in-original-place? in-original-place?-expr | #:blocking? blocking?-expr | #:callback-exns? callback-exns?-expr | #:retry (retry-id [arg-id init-expr]) maybe-args =
| formals :: type-spec = type-expr | (id : type-expr) | (type-expr = value-expr) | (id : type-expr = value-expr) maybe-wrapper =
| -> output-expr
In the simplest form of _fun, only the input type-exprs and the output type-expr are specified, and each types is a simple expression, which creates a straightforward function type. For example,
specifies a function that receives a string and an integer and returns an integer.
See _cprocedure for information about the #:abi, #:varargs-after, #:save-errno, #:keep, #:atomic?, #:async-apply, #:in-original-place?, #:blocking, and #:callback-exns? options.
The full form of each argument type-spec can include an optional label and an expression. A label id : makes the argument value accessible to later expressions using id. A = value-expr expression causes the wrapper function to calculates the argument for that position using value-expr, implying that the wrapper does not expect to be given an argument for that position.
For example,
produces a wrapper that takes a single string argument and calls a foreign function that takes a string and an integer; the string’s length is provided as the integer argument.
If the optional output-expr is specified, or if an expression is provided for the output type, then the expression specifies an expression that will be used as a return value for the function call, replacing the foreign function’s result. The output-expr can use any of the previous labels, including a label given for the output to access the foreign function’s return value.
For example,
produces a wrapper that returns the minimum of the foreign function’s result and the given integer argument.
A #:retry (retry-id [arg-id init-expr] ...) specification binds retry-id for use in an output-expr for retrying the foreign call (normally in tail position). The function bound to retry-id accepts each arg-id as an argument, each arg-id can be used in = value-exprs, and each init-exprs provides the initial value for the corresponding arg-id.
For example,
(_fun #:retry (again [count 0]) _string _int -> (r : _int) -> (if (and (= r ERR_BUSY) (< count 5)) (again (add1 count)) r)) produces a wrapper that calls the foreign function up to five times if it continues to produce a number equal to ERR_BUSY.
In rare cases where complete control over the input arguments is needed, the wrapper’s argument list can be specified as maybe-args with a formals as for lambda (including keyword arguments and/or a “rest” argument). When an argument type-spec includes a label that matches an binding identifier in formals, then the identifier is used as the default value for the argument. All argument type-specs must include either explicit = value-expr annotations or an implicit one through a matching label.
For example,
produces a wrapper that receives an integer and a string, but the foreign function receives the string first.
Changed in version 6.2 of package base: Added the #:retry option.
Changed in version 6.3: Added the #:lock-name option.
Changed in version 6.12.0.2: Added the #:blocking? option.
Changed in version 7.9.0.16: Added the #:varargs-after option.
Changed in version 8.0.0.8: Added the #:callback-exns? option.
procedure
(function-ptr ptr-or-proc fun-type) → cpointer?
ptr-or-proc : (or cpointer? procedure?) fun-type : ctype?
syntax
3.6.1 Custom Function Types
The behavior of the _fun type can be customized via custom function types, which are pieces of syntax that can behave as C types and C type constructors, but they can interact with function calls in several ways that are not possible otherwise. When the _fun form is expanded, it tries to expand each of the given type expressions, and ones that expand to certain keyword-value lists interact with the generation of the foreign function wrapper. This expansion makes it possible to construct a single wrapper function, avoiding the costs involved in compositions of higher-order functions.
Custom function types are macros that expand to a sequence (key: val ...), where each key: is from a short list of known keys. Each key interacts with generated wrapper functions in a different way, which affects how its corresponding argument is treated:
type: specifies the foreign type that should be used, if it is #f then this argument does not participate in the foreign call.
expr: specifies an expression to be used for arguments of this type, removing it from wrapper arguments.
bind: specifies a name that is bound to the original argument if it is required later (e.g., _box converts its associated value to a C pointer, and later needs to refer back to the original box).
1st-arg: specifies a name that can be used to refer to the first argument of the foreign call (good for common cases where the first argument has a special meaning, e.g., for method calls).
prev-arg: similar to 1st-arg:, but refers to the previous argument.
pre: a pre-foreign code chunk that is used to change the argument’s value.
post: a similar post-foreign code chunk.
keywords: specifies keyword/value expressions that will be used with the surrounding _fun form. (Note: the keyword/value sequence follows keywords:, not parenthesized.)
The pre: and post: bindings can be of the form (id => expr) to use the existing value. Note that if the pre: expression is not (id => expr), then it means that there is no input for this argument to the _fun-generated procedure. Also note that if a custom type is used as an output type of a function, then only the post: code is used.
Most custom types are meaningful only in a _fun context, and will raise a syntax error if used elsewhere. A few such types can be used in non-_fun contexts: types which use only type:, pre:, post:, and no others. Such custom types can be used outside a _fun by expanding them into a usage of make-ctype, using other keywords makes this impossible, because it means that the type has specific interaction with a function call.
syntax
(define-fun-syntax id transformer-expr)
For instance, the following defines a new type that automatically coerces the input number to an inexact form which is compatible with the _float type.
(define-fun-syntax _float* (syntax-id-rules (_float*) [_float* (type: _float pre: (x => (+ 0.0 x)))])) (_fun _float* -> _bool)
syntax
Examples:
(_fun _? ; not sent to foreign function _int -> _int) (_fun [init : _?] ; init is used for pre-processing [boxed : (_box _int) = (box init)] -> _void) (_fun [offset : _?] ; offset is used for post-processing -> [res : _int] -> (+ res offset))
syntax
(_ptr mode type-expr maybe-malloc-mode)
mode = i | o | io maybe-malloc-mode =
| #f | raw | atomic | nonatomic | tagged | atomic-interior | interior | zeroed-atomic | zeroed-atomic-interior | stubborn | uncollectable | eternal
i —
indicates an input pointer argument: the wrapper arranges for the function call to receive a value that can be used with the type and to send a pointer to this value to the foreign function. After the call, the value is discarded. o —
indicates an output pointer argument: the foreign function expects a pointer to a place where it will save some value, and this value is accessible after the call, to be used by an extra return expression. If _ptr is used in this mode, then the generated wrapper does not expect an argument, since one will be freshly allocated before the call. io —
combines the above into an input/output pointer argument: the wrapper gets the Racket value, allocates and set a pointer using this value, and then references the value after the call. The “_ptr” name can be confusing here: it means that the foreign function expects a pointer, but the generated wrapper uses an actual value. (Note that if this is used with structs, a struct is created when calling the function, and a copy of the return value is made too— which is inefficient, but ensures that structs are not modified by C code.)
For example, the _ptr type can be used in output mode to create a foreign function wrapper that returns more than a single argument. The following type:
(_fun (i : (_ptr o _int)) -> (d : _double) -> (values d i))
creates a function that calls the foreign function with a fresh integer pointer, and use the value that is placed there as a second return value.
The pointer argument created by _ptr is allocated using allocated using (malloc type-expr) if maybe-malloc-mode is not specified or if it is #f, (malloc type-expr 'maybe-malloc-mode) otherwise.
Changed in version 7.7.0.6 of package base: The modes i, o,
and io match as symbols
instead of free identifiers.
Changed in version 8.0.0.13: Added maybe-malloc-mode.
Changed in version 8.14.0.4: Added the zeroed-atomic and
zeroed-atomic-interior allocation modes.
syntax
(_box type maybe-malloc-mode)
Example:
(_fun (_box _int) -> _void) (_fun [boxed : (_box _int) = (box 0)] -> [res : _int] -> (values res (unbox boxed)))
syntax
(_list mode type maybe-len maybe-mode)
mode = i | o | io maybe-len =
| len-expr maybe-mode =
| atomic | raw | atomic | nonatomic | tagged | atomic-interior | interior | zeroed-atomic | zeroed-atomic-interior | stubborn | uncollectable | eternal
For example, the following type corresponds to a function that takes a vector argument of type *float (from a Racket list input) and a length argument of type int for the vector:
(_fun [vec : (_list i _float)] ; this argument is implicitly provided [_int = (length vec)] -> _void)
In this next example, the type specifies a function that provides output through a given output vector (represented as a list on the Racket side) and through a boolean return value. The FFI-bound function will take an integer argument and return two values, the vector and the boolean.
(_fun [len : _int] [vec : (_list o _float len)] -> [res : _bool] -> (values vec res))
Changed in version 7.7.0.2 of package base: Added maybe-mode.
Changed in version 7.7.0.6: The modes i, o,
and io match as symbols
instead of free identifiers.
Changed in version 8.14.0.4: Added the zeroed-atomic
zeroed-atomic-interior allocation modes.
syntax
(_vector mode type maybe-len maybe-mode)
Examples:
(_fun [vec : (_vector i _float)] [_int = (length vec)] -> _void) (_fun [len : _int] [vec : (_vector o _float len)] -> [res : _bool] -> (values vec res))
See _list for more explanation about the examples.
Changed in version 7.7.0.2 of package base: Added maybe-mode.
Changed in version 7.7.0.6: The modes i, o,
and io match as symbols
instead of free identifiers.
In the BC implementation of Racket, a C non-NULL result value is converted to a Racket byte string without copying; the pointer is treated as potentially managed by the garbage collector (see _gcpointer for caveats). In the CS implementation of Racket, conversion requires copying to represent a C char* result as a Racket byte string, and the original pointer is not treated as managed by the garbage collector. In both cases, the C result must have a nul terminator to determine the Racket byte string’s length.
A (_bytes o len-expr) form is a custom function type. As an argument, a byte string is allocated with the given length; in the BC implementation, that byte string includes an extra byte for the nul terminator, and (_bytes o len-expr) as a result wraps a C non-NULL char* pointer as a byte string of the given length. For the CS implementation, the allocated argument does not include a nul terminator and a copy is made for a result string.
As usual, _bytes treats #f as NULL and vice versa. As a result type, (_bytes o len-expr) works only for non-NULL results.
syntax
(_bytes/nul-terminated o len-expr)
When (_bytes/nul-terminated o len-expr) is used as an argument type, a byte string of length len-expr is allocated. Similarly, when (_bytes/nul-terminated o len-expr) is used as a result type, a char* result is copied to a fresh byte string of length len-expr.
As usual, _bytes/nul-terminated treats #f as NULL and vice versa. As a result type, (_bytes/nul-terminated o len-expr) works only for non-NULL results.
Added in version 6.12.0.2 of package base.