Foreign-Function Interface to C
MzLib's foreign.ss provides an interface to dynamic C libraries that requires no C compiler and works completely at run time. See PLT Foreign Interface Manual for more information. The manual Inside PLT MzScheme, meanwhile, describes a C-level API for extending MzScheme. This section describes the cffi.ss library of the compiler collection, which provides a third alternative (in conjuction with mzc).
The cffi.ss library relies on a C compiler to statically construct an interface to C code through directives embedded in a Scheme program. The library implements a subset of Gambit-C's foreign-function interface (see Marc Feeley's Gambit-C, version 3.0).
The cffi.ss module defines three
forms: c-lambda
, c-declare
, and c-include
. When
interpreted directly or compiled to byte code, c-lambda
produces a function that always raises exn:fail
,
and c-declare
and c-include
raise exn:fail
. When compiled by mzc, the forms provide
access to C. The mzc compiler implicitly imports cffi.ss
into the top-level environment.
The c-lambda
form creates a Scheme procedure whose body is
implemented in C. Instead of declaring argument names, a
c-lambda
form declares argument types, as well as a return
type. The implementation can be simply the name of a C function, as
in the following definition of fmod
:
(define fmod (c-lambda (double
double
)double
"fmod"))
Alternatively, the implementation can be C code to serve as the body of a function, where the arguments are bound to ___arg1 (three underscores), etc., and the result is installed into ___result (three underscores):
(define machine-string->float (c-lambda (char-string
)float
"___result = *(float *)___arg1;"))
The c-lambda
form provides only limited conversions between C and
Scheme data. For example, the following function does not reliably
produce a string of four characters:
(define broken-machine-float->string (c-lambda (float
)char-string
"char b[5]; *(float *)b = ___arg1; b[4] = 0; ___result = b;"))
because the representation of a float can contain null bytes, which terminate the string. However, the full MzScheme API, which is described in Inside PLT MzScheme, can be used in a function body:
(define machine-float->string (c-lambda (float
)scheme-object
"char b[4]; *(float *)b = ___arg1; ___result = scheme_make_sized_byte_string(b, 4, 1);"))
The c-declare
form declares arbitrary C code to appear after
escheme.h or scheme.h is included, but before any other
code in the compilation environment of the declaration. It is often
used to declare C header file inclusions. For example, a proper
definition of fmod
needs the math.h header file:
(c-declare "#include <math.h>") (define fmod (c-lambda (double
double
)double
"fmod"))
The c-declare
form can also be used to define helper C
functions to be called through c-lambda
.
The c-include
form expands to a c-declare
form
using the content of a specified file. Use (c-include file)
instead of (
when it's easier to have MzScheme resolve the file path than to have
the C compiler resolve it.c-declare
"#include
)file
"
The plt/collects/mzscheme/examples directory in the PLT distribution contains additional examples.
When compiling for MzScheme3m (see Inside PLT MzScheme), C code
inserted by c-lambda
, c-declare
, and c-include
will be transformed in the same was as mzc's --xform
mode (which may or may not be enough to make the code work correctly
in MzScheme3m; see Inside PLT MzScheme for more information).
The c-lambda
, c-declare
, and c-include
forms are
defined as follows:
(c-lambda (argument-type ···) result-type funcname-or-body-string)
creates a Scheme procedure whose body is implemented in C. The procedure takes as many arguments as the suppliedargument-type
s, and it returns one value. Ifreturn-type
isvoid
, the procedure's result is always void. Thefuncname-or-body-string
is either the name of a C function (or macro) or the body of a C function.If
funcname-or-body-string
is a string containing only alphanumeric characters and _, then the created Scheme procedure passes all of its arguments to the named C function (or macro) and returns the function's result. Each argument to the Scheme procedure is converted according to the correspondingargument-type
(as described below) to produce an argument to the C function. Unlessreturn-type
isvoid
, the C function's result is converted according toreturn-type
for the Scheme procedure's result.If
funcname-or-body-string
contains more than alphanumeric characters and _, then it must contain C code to implement the function body. The converted arguments for the function will be in variables ___arg1, ___arg2, ... (with three underscores in each name) in the context where thefuncname-or-body-string
is placed for compilation. Unlessreturn-type
isvoid
, thefuncname-or-body-string
code should assign a result to the variable ___result (three underscores), which will be declared but not initialized. Thefuncname-or-body-string
code should not return explicitly; control should always reach the end of the body. If thefuncname-or-body-string
code defines the pre-processor macro ___AT_END (with three leading underscores), then the macro's value should be C code to execute after the value ___result is converted to a Scheme result, but before the result is returned, all in the same block; defining ___AT_END is primarily useful for deallocating a string in ___result that has been copied by conversion. Thefuncname-or-body-string
code will start on a new line at the beginning of a block in its compilation context, and ___AT_END will be undefined after the code.In addition to ___arg1, etc., the variable argc is bound in
funcname-or-body-string
to the number of arguments supplied to the function, and argv is bound to a Scheme_Object* array of length argc containing the function arguments as Scheme values. The argv and argc variables are mainly useful for error reporting (e.g., with scheme_wrong_type).Each
argument-type
must be one of the following:bool
Scheme range: any value
C type: int
Scheme to C conversion:#f
=> 0, anything else => 1
C to Scheme conversion: 0 =>#f
, anything else =>#t
char
Scheme range: character
C type: char
Scheme to C conversion: character's ASCII value cast to signed byte
C to Scheme conversion: ASCII value from unsigned cast mapped to characterunsigned-char
Scheme range: character
C type: unsigned char
Scheme to C conversion: character's ASCII value
C to Scheme conversion: ASCII value mapped to charactersigned-char
Scheme range: character
C type: signed char
Scheme to C conversion: character's ASCII value cast to signed byte
C to Scheme conversion: ASCII value from unsigned cast mapped to characterint
Scheme range: exact integer that fits into an int
C type: int
conversions: (obvious and precise)unsigned-int
Scheme range: exact integer that fits into an unsigned int
C type: unsigned int
conversions: (obvious and precise)long
Scheme range: exact integer that fits into a long
C type: long
conversions: (obvious and precise)unsigned-long
Scheme range: exact integer that fits into an unsigned long
C type: unsigned long
conversions: (obvious and precise)short
Scheme range: exact integer that fits into a short
C type: short
conversions: (obvious and precise)unsigned-short
Scheme range: exact integer that fits into an unsigned short
C type: unsigned short
conversions: (obvious and precise)float
Scheme range: real number
C type: float
Scheme to C conversion: number converted to inexact and cast to float
C to Scheme conversion: cast to double and encapsulated as an inexact numberdouble
Scheme range: real number
C type: double
Scheme to C conversion: number converted to inexact
C to Scheme conversion: encapsulated as an inexact numberchar-string
Scheme range: byte string or#f
C type: char*
Scheme to C conversion: string => contained byte-array pointer,#f
=> NULL
C to Scheme conversion: NULL =>#f
, anything else => new byte string created by copying the stringnonnull-char-string
Scheme range: byte string
C type: char*
Scheme to C conversion: byte string's contained byte-array pointer
C to Scheme conversion: new byte string created by copying the stringscheme-object
Scheme range: any value
C type: Scheme_Object*
Scheme to C conversion: no conversion
C to Scheme conversion: no conversion(
pointer
bytes
)
Scheme range: an opaque c-pointer value, identified as typebytes
, or#f
C type:bytes
*
Scheme to C conversion:#f
=> NULL, c-pointer => contained pointer cast tobytes
*
C to Scheme conversion: NULL =>#f
, anything else => new c-pointer containing the pointer and identified as typebytes
The
return-type
must bevoid
or one of thearg-type
keywords.(c-declare code-string)
declares arbitrary C code to appear after escheme.h or scheme.h is included, but before any other code in the compilation environment of the declaration. Ac-declare
form can appear only at the top-level or within a module's top-level sequence.The
code-string
code will appear on a new line in the file for C compilation. Multiplec-include
declarations are concatenated (with newlines) in order to produces a sequence of declarations.(c-include path-spec)
expands to a use ofc-declare
with the content ofpath-spec
. Thepath-spec
has the same form as forinclude
in MzLib's include.ss.