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 (c-declare "#include file") when it's easier to have MzScheme resolve the file path than to have the C compiler resolve it.

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: