Chapter 4. Foreign Interface

Chez Scheme provides two ways to interact with "foreign" code, i.e., code written in other languages. The first is via subprocess creation and communication, which is discussed in the Section 4.1. The second is via static or dynamic loading and invocation from Scheme of procedures written in C and invocation from C of procedures written in Scheme. These mechanisms are discussed in Sections 4.2 through 4.7.

The method for static loading of C object code is dependent upon which machine you are running; see the installation instructions distributed with Chez Scheme.

Section 4.1. Subprocess Communication

Two procedures, system and process, are used to create subprocesses. Both procedures accept a single string argument and create a subprocess to execute the shell command contained in the string. The system procedure waits for the process to exit before returning, however, while the process procedure returns immediately without waiting for the process to exit. The standard input and output files of a subprocess created by system may be used to communicate with the user's console. The standard input and output files of a subprocess created by process may be used to communicate with the Scheme process.


procedure: (system command)
returns: unspecified

command must be a string.

The system procedure creates a subprocess to perform the operation specified by command. The subprocess may communicate with the user through the same console input and console output files used by the Scheme process. After creating the subprocess, system waits for the process to exit before returning.


procedure: (process command)
returns: see explanation

command must be a string.

The process procedure creates a subprocess to perform the operation specified by command. Unlike system, process returns immediately after creating the subprocess with a list of three elements:

  1. process-input-port is an input port from which Scheme can read input sent to Scheme by the subprocess through its standard output file.

  2. process-output-port is an output port to which Scheme can send output to the subprocess through its standard input file.

  3. process-id is an integer identifying the created subprocess provided by the host operating system.

If the process exits or closes its standard output port, any procedure that reads input from the process-input-port, e.g., read-char, will return an end-of-file object. The predicate char-ready? may be used to detect whether input has been sent by the subprocess to Scheme.

It is sometimes necessary to force output to be sent immediately to the subprocess by invoking flush-output-port on the process-output-port, since Scheme buffers the output for efficiency.

The process procedure should be used to interact with complete and separate programs, where the limited interaction provided by communication through input and output ports provides sufficient functionality and efficiency.

On UNIX systems, the process-id is the process identifier for the shell created to execute command. If command is used to invoke an executable file rather than a shell command, it may be useful to prepend command with the string "exec ", which causes the shell to load and execute the named executable directly, without forking a new process---the shell equivalent of a tail call. This will reduce by one the number of subprocesses created and cause process-id to reflect the process identifier for the executable once the shell has transferred control.

Section 4.2. Foreign Procedures

Chez Scheme's foreign-procedure interface allows a Scheme program to invoke procedures written in C or in languages that obey the same calling conventions as C. Two steps are necessary before foreign procedures can be invoked from Scheme. First, the foreign procedure must be compiled and loaded, either statically or dynamically, as described in Section 4.3. Then, access to the foreign procedure must be established in Scheme, as described in this section. Once access to a foreign procedure has been established it may be called as an ordinary Scheme procedure.

Since foreign procedures operate independently of the Scheme memory management and exception handling system, great care must be taken when using them. Although the foreign-procedure interface provides type checking (at optimize levels less than 3) and type conversion, the programmer must ensure that the sharing of data between Scheme and foreign procedures is done safely by specifying proper argument and result types.


syntax: (foreign-procedure entry-exp (param-type ...) res-type)
syntax: (foreign-procedure conv entry-exp (param-type ...) res-type)
returns: a procedure

entry-exp must evaluate to a string representing a valid foreign procedure entry point or an integer representing the address of the foreign procedure. The param-types and res-type must be symbols or structured forms as described below. When a foreign-procedure expression is evaluated, a Scheme procedure is created that will invoke the foreign procedure specified by entry-exp. When the procedure is called each argument is checked and converted according to the specified param-type before it is passed to the foreign procedure. The result of the foreign procedure call is converted as specified by the res-type. Multiple procedures may be created for the same foreign entry.

If conv is present, it specifies the calling convention to be used. The default is #f, which specifies the default calling convention on the target machine. Three other conventions are currently supported, all only under Windows: __stdcall, __cdecl, and __com. Since __cdecl is the default, specifying __cdecl is equivalent to specifying #f or no convention.

Use __stdcall to access most Windows API procedures. Use __cdecl for Windows API varargs procedures, for C library procedures, and for most other procedures. Use __com to invoke COM interface methods; COM uses the __stdcall convention but additionally performs the indirections necessary to obtain the correct method from a COM instance. The address of the COM instance must be passed as the first argument, which should normally be declared as iptr. For the __com interface only, entry-exp must evaluate to the byte offset of the method in the COM vtable. For example,

(foreign-procedure __com 12 (iptr double-float) integer-32)

creates an interface to a COM method at offset 12 in the vtable encapsulated within the COM instance passed as the first argument, with the second argument being a double float and the return value being an integer.

Complete type checking and conversion is performed on the parameters. The types scheme-object and string must be used with caution, however, since they allow allocated Scheme objects to be used in places the Scheme memory management system cannot control. No problems will arise as long as such objects are not retained in foreign variables or data structures while Scheme code is running, since garbage collection can occur only while Scheme code is running. All other parameter types are converted to equivalent foreign representations and consequently can be retained indefinitely in foreign variables and data structures. Following are the valid parameter types:

integer-32: Exact integers from -231 through 232 - 1 are valid. Integers in the range 231 through 232 - 1 are treated as two's complement representations of negative numbers, e.g., #xffffffff is treated as -1. The argument is passed to C as an int of the appropriate size.

unsigned-32: Exact integers from -231 to 232 - 1 are valid. Integers in the range -231 through -1 are treated as the positive equivalents of their two's complement representation, e.g., -1 is treated as #xffffffff. The argument is passed to C as an unsigned int of the appropriate size.

integer-64: (64-bit platforms only) Exact integers from -263 through 264 - 1 are valid. Integers in the range 263 through 264 - 1 are treated as two's complement representations of negative numbers. The argument is passed to C as a 64-bit int.

unsigned-64: (64-bit platforms only) Only exact integers from -263 through 264 - 1 are valid. Integers in the range -231 through -1 are treated as the positive equivalents of their two's complement representation, The argument is passed to C as a 64-bit int.

iptr: This type is equivalent to integer-32 on 32-bit platforms and integer-64 on 64-bit platforms.

uptr: This type is equivalent to unsigned-32 on 32-bit platforms and unsigned-64 on 64-bit platforms.

fixnum: Only fixnums are valid. A fixnum argument is converted into the appropriate size of C int: a 32-bit integer on 32-bit platforms and a 64-bit integer on 64-bit platforms. Transmission of fixnums is slightly faster than transmission of integer-32 and unsigned-32 values.

boolean: Any Scheme object may be passed as a boolean. #f is converted to 0; all other objects are converted to 1. The argument is passed to C as an int.

char: Only Scheme characters are valid char parameters. The character is converted to its standard byte representation, as with char->integer. The argument is passed to C as an int.

(void *): This type is equivalent to unsigned-32 on platforms that represent pointers (specifically void * values in C) as 32-bit values and unsigned-64 on platforms that represent pointers as 64-bit values.

string: Only Scheme strings or #f are valid. For #f, the null pointer (0) is passed to the foreign procedure. Otherwise, a pointer to the first character in the string is passed. Since Scheme strings are terminated by a hidden null byte, standard C procedures can be used to process them. The strings should not be retained in foreign variables or data structures, however, since the memory management system may relocate or discard them between foreign procedure calls.

double-float: Only Scheme flonums are valid---other Scheme numeric types are not automatically converted. The parameter is passed using the standard foreign mechanism for floating point arguments.

single-float: Only Scheme flonums are valid---other Scheme numeric types are not automatically converted. The parameter is passed using the standard foreign mechanism for single floating-point arguments. Since Chez Scheme represents flonums in double-float format, the parameter is first converted into single-float format.

scheme-object: The parameter is passed directly to the foreign procedure; no conversion or type checking is performed. This form of parameter passing should be used with discretion. Scheme objects should not be preserved in foreign variables or data structures since the memory management system may relocate them between foreign procedure calls.

The result types are similar to the parameter types with the addition of a void type. In general, the type conversions are the inverse of the parameter type conversions, with the notable exception of the string type. No error checking is performed on return, since the system cannot determine whether a foreign result is actually of the indicated type. Particular caution should be exercised with the result types scheme-object, string and double-float, since invalid return values may lead to invalid memory references as well as incorrect computations. Following are the valid result types:

void: The result of the foreign procedure call is ignored and an unspecified Scheme object is returned. void should be used when foreign procedures are called for effect only.

integer-32: The result is interpreted as a signed integer and is converted to a Scheme exact integer. The value is returned from C as a 32-bit int.

unsigned-32: The result is interpreted as an unsigned integer and is converted to a Scheme nonnegative exact integer. The value is returned from C as a 32-bit int.

integer-64: The result is interpreted as a signed integer and is converted to a Scheme exact integer. The value is returned from C as a 64-bit int.

unsigned-64: The result is interpreted as an unsigned integer and is converted to a Scheme nonnegative exact integer. The value is returned from C as a 64-bit int.

iptr: This type is equivalent to integer-32 on 32-bit platforms and integer-64 on 64-bit platforms.

uptr: This type is equivalent to unsigned-32 on 32-bit platforms and unsigned-64 on 64-bit platforms.

fixnum: The result is interpreted as a signed integer and is converted to a Scheme fixnum. The value is returned from C as an int of the appropriate size: 32 bits on 32-bit platforms and 64 bits on 64-bit platforms.

boolean: Any foreign value may be returned as a boolean. 0 is converted to #f; all other values are converted to #t. The value is returned from C as an int.

char: The low-order byte of the result is converted to a Scheme character. The result must be in the range -128 to +255. The value is returned from C as an int.

string: The result is interpreted as a pointer to a null-terminated string. The string is copied into a new Scheme string. If the result is a null (0) pointer #f is returned. The effect is unspecified if the result is not a valid string pointer or a null pointer. Typically such an error would manifest itself as an invalid memory reference.

double-float: The result is interpreted as a double float and is translated into a Chez Scheme flonum. If the foreign procedure does not return a valid double float using the standard mechanism the effect is unspecified.

single-float: The result is interpreted as a single float and is translated into a Chez Scheme flonum. If the foreign procedure does not return a valid single float using the standard mechanism the effect is unspecified. Since Chez Scheme represents flonums in double-float format, the result is first converted into double-float format.

scheme-object: The result is assumed to be a valid Scheme object, and no conversion is performed. This type is inherently dangerous, since an invalid Scheme object can corrupt the memory management system with unpredictable (but always unpleasant) results. Since Scheme objects are actually typed pointers, even integers cannot be safely returned as type scheme-object unless they were created by the Scheme system.

Consider a C identity procedure:

int id(x) int x; { return x; }

After a file containing this procedure has been compiled and loaded (see Section 4.3) it can be accessed as follows:

(foreign-procedure "id"
  (integer-32) integer-32) <graphic> #<procedure>
((foreign-procedure "id"
   (integer-32) integer-32)
 1) <graphic> 1
(define int-id
  (foreign-procedure "id"
    (integer-32) integer-32))
(int-id 1) <graphic> 1

By using different parameter and result types the "id" entry can be interpreted in different ways. For instance, it can also be interpreted as a boolean or character identity procedure:

(define char-id
  (foreign-procedure "id"
    (char) char))
(char-id #\a) <graphic> #\a
(define bool-id
  (foreign-procedure "id"
    (boolean) boolean))
(bool-id #f) <graphic> #f
(bool-id #t) <graphic> #t
(bool-id 1) <graphic> #t

As the last example reveals, bool-id is actually a conversion procedure. When a Scheme object is passed as type boolean it is converted to 0 or 1, and when it is returned it is converted to #f or #t. As a result objects are converted to normalized boolean values. The "id" entry can be used to create other conversion procedures by varying the type specifications:

(define int->bool
  (foreign-procedure "id"
    (integer-32) boolean))
(int->bool 0) <graphic> #f
(int->bool 5) <graphic> #t
(map (foreign-procedure "id"
       (boolean) integer-32)
     '(#t #f)) <graphic> (1 0)
(define char->int
  (foreign-procedure "id"
    (char) integer-32))
(char->int #\nul) <graphic> 0
(define void
  (foreign-procedure "id"
    (integer-32) void))
(void 10) <graphic> unspecified

A foreign entry is resolved when a foreign-procedure expression is evaluated, rather than either when the code is loaded or each time the procedure is invoked. Thus, the following definition is always valid since the foreign-procedure expression is not immediately evaluated:

(define doit
  (lambda ()
    ((foreign-procedure "doit" () void))))

It is an error to invoke doit, however, before an entry for "doit" has been provided. Similarly, an entry for "doit" must exist before the following code is evaluated:

(define doit
  (foreign-procedure "doit" () void))

Although the second definition is more constraining on the load order of foreign files, it is more efficient since the entry resolution need be done only once.

It is often useful to define a template to be used in the creation of several foreign procedures with similar argument types and return values. For example, the following code creates two foreign procedures from a single foreign procedure expression, by abstracting out the foreign procedure name:

(define double->double
  (lambda (proc-name)
    (foreign-procedure proc-name
      (double-float)
      double-float)))

(define log10 (double->double "log10"))
(define gamma (double->double "gamma"))

Both "log10" and "gamma" must be available as foreign entries (see Section 4.3) before the corresponding definitions. The use of foreign procedure templates can simplify the coding process and reduce the amount of code generated when a large number of foreign procedures are involved, e.g., when an entire library of foreign procedures is imported into Scheme.

Section 4.3. Providing Access to Foreign Procedures

Access to foreign procedures can be provided in several ways:


procedure: (foreign-entry? entry-name)
returns: #t if entry-name is an existing foreign procedure entry point, #f otherwise

entry-name must be a string. foreign-entry? may be used to determine if an entry exists for a foreign procedure.

(foreign-entry? "strlen") <graphic> #t
((foreign-procedure "strlen"
    (string) integer-32)
 "hey!") <graphic> 4


procedure: (load-shared-object filename)
returns: unspecified

filename must be a string. load-shared-object loads the shared object named by filename. Shared objects may be system libraries or files created from ordinary C programs. All external symbols in the shared object, along with external symbols available in other shared objects linked with the shared object, are made available as foreign entries.

This procedure is supported for most platforms upon which Chez Scheme runs.

If filename does not begin with a "." or "/", the shared object is searched for in a default set of directories determined by the system.

On most Unix systems, load-shared-object is based on the system routine dlopen. Under Windows, load-shared-object is based on LoadLibrary. Refer to the documentation for these routines and for the C compiler and loader for precise rules for locating and building shared objects.

load-shared-object can be used to access built-in C library functions, such as getenv. The name of the shared object varies from one system to another. On Linux and FreeBSD systems:

(load-shared-object "libc.so.6")

On Solaris and OpenBSD systems:

(load-shared-object "libc.so")

On MacOS X systems:

(load-shared-object "libc.dylib")

On Windows:

(load-shared-object "crtdll.dll")

Once the C library has been loaded, getenv should be available as a foreign entry.

(foreign-entry? "getenv") <graphic> #t

An equivalent Scheme procedure may be defined and invoked as follows.

(define getenv
  (foreign-procedure "getenv"
    (string)
    string))
(getenv "HOME") <graphic> "/home/elmer/fudd"
(getenv "home") <graphic> #f

load-shared-object can be used to access user-created libraries as well. Suppose the C file "even.c" contains

int even(n) int n; { return n == 0 || odd(n - 1); }

and the C file "odd.c" contains

int odd(n) int n; { return n != 0 && even(n - 1); }

The files must be compiled and linked into a shared object before they can be loaded. How this is done depends upon the host system. On Linux, FreeBSD, and OpenBSD systems:

(system "cc -fPIC -shared -o evenodd.so even.c odd.c")

On MacOS X (Intel or PowerPC) systems:

(system "cc -dynamiclib -o evenodd.so even.c odd.c")

On 32-bit (Chez Scheme machine type "sps2") Sparc Solaris:

(system "cc -KPIC -G -o evenodd.so even.c odd.c")

On 64-bit (Chez Scheme machine type "sparcv9") Sparc Solaris:

(system "cc -xarch=v9 -KPIC -G -o evenodd.so even.c odd.c")

On Windows, we build a DLL (dynamic link library) file. In order to make the compiler generate the appropriate entry points, we alter even.c to read

#ifdef WIN32
#define EXPORT extern __declspec (dllexport)
#else
#define EXPORT extern
#endif

EXPORT int even(n) int n; { return n == 0 || odd(n - 1); }

and odd.c to read

#ifdef WIN32
#define EXPORT extern __declspec (dllexport)
#else
#define EXPORT extern
#endif

EXPORT int odd(n) int n; { return n != 0 && even(n - 1); }

We can then build the DLL as follows, giving it the extension ".so" rather than ".dll" for consistency with the other systems.

(system "cl -c -DWIN32 even.c")
(system "cl -c -DWIN32 odd.c")
(system "link -dll -out:evenodd.so even.obj odd.obj")

The resulting ".so" file can be loaded into Scheme and even and odd made available as foreign procedures:

(load-shared-object "./evenodd.so")
(let ([odd (foreign-procedure "odd"
             (integer-32) boolean)]
      [even (foreign-procedure "even"
              (integer-32) boolean)])
  (list (even 100) (odd 100))) <graphic> (#t #f)

The filename is given as "./evenodd.so" rather than simply "evenodd.so", because some systems look for shared libraries in a standard set of system directories that does not include the current directory.


procedure: (remove-foreign-entry entry-name)
returns: unspecified

remove-foreign-entry blocks further access to the entry specified by the string entry-name. It is an error if the entry does not exist. Since access previously established by foreign-procedure is not affected, remove-foreign-entry may be used to clean up after the desired interface to a group of foreign procedures has been established.

remove-foreign-entry can only be used to remove entries registered using Sforeign_symbol and Sregister_symbol. It cannot be used to remove an entry created as a result of a call to load-shared-object.

Section 4.4. Using Other Foreign Languages

Although the Chez Scheme foreign procedure interface is oriented primarily toward procedures defined in C or available in C libraries, it is possible to invoke procedures defined in other languages that follow C calling conventions. One source of difficulty may be the interpretation of names. Since Unix-based C compilers often prepend an underscore to external names, the foreign interface attempts to interpret entry names in a manner consistent with the host C compiler. Occasionally, such as for assembly coded files, this entry name interpretation may not be desired. It can be prevented by prefixing the entry name with an "=" character. For example, after loading an assembly file containing a procedure "foo" one might have:

(foreign-entry? "foo") <graphic> #f
(foreign-entry? "=foo") <graphic> #t

Section 4.5. Calling into Scheme

Section 4.2 described the foreign-procedure form, which permits Scheme code to invoke C or C-compatible foreign procedures. This section describes the foreign-callable form, which permits C or C-compatible code to call Scheme procedures. A more primitive mechanism for calling Scheme procedures from C is described in Section 4.6.

As when calling foreign procedures from Scheme, great care must be taken when sharing data between Scheme and foreign code that calls Scheme to avoid corrupting Scheme's memory management system.


syntax: (foreign-callable proc-exp (param-type ...) res-type)
syntax: (foreign-callable conv proc-exp (param-type ...) res-type)
returns: a code object

proc-exp must evaluate to a procedure, the Scheme procedure that is to be invoked by foreign code. The parameter and result types are as described for foreign-procedure in Section 4.2, except that string is not a permissible result type. If conv is present, it specifies the calling convention to be used. foreign-callable supports the same conventions as foreign-procedure with the exception of __com.

Type checking is performed for result values but not argument values, since the parameter values are provided by the foreign code and must be assumed to be correct.

The value produced by foreign-callable is a Scheme code object, which contains some header information as well as code that performs the call to the encapsulated Scheme procedure. The code object may be converted into a foreign-callable address via foreign-callable-entry-point, which returns an integer representing the address of the entry point within the code object. (The C-callable library function Sforeign_callable_entry_point, described in Section 4.6, may be used to obtain the entry point as well.) This is an implicit pointer into a Scheme object, and in many cases, it is necessary to lock the code object (using lock-object) before converting it into an entry point to prevent Scheme's storage management system from relocating or destroying the code object, e.g., when the entry point is registered as a callback and retained in the "C" side indefinitely.

The following code creates a foreign-callable code object, locks the code object, and returns the entry point.

(let ([x (foreign-callable
           (lambda (x y) (pretty-print (cons x (* y 2))))
           (string integer-32)
           void)])
  (lock-object x)
  (foreign-callable-entry-point x))

Unless the entry point is intended to be permanent, a pointer to the code object returned by foreign-callable should be retained so that it can be unlocked when no longer needed.

Mixed use of foreign-callable and foreign-procedure may result in nesting of foreign and Scheme calls, and this results in some interesting considerations when continuations are involved, directly or indirectly (as via the default error handler). See Section 4.7 for a discussion of the interaction between foreign calls and continuations.

The following example demonstrates how the "callback" functions required by many windowing systems might be defined in Scheme with the use of foreign-callable. Assume that the following C code has been compiled and loaded (see Section 4.3).

#include <stdio.h>

typedef void (*CB)(char);

CB callbacks[256];

void cb_init(void) {
   int i;

   for (i = 0; i < 256; i += 1)
       callbacks[i] = (CB)0;
}

void register_callback(char c, int cb) {
    callbacks[c] = (CB)cb;
}

void event_loop(void) {
    CB f; char c;

    for (;;) {
        c = getchar();
        if (c == EOF) break;
        f = callbacks[c];
        if (f != (CB)0) f(c);
    }
}

Interfaces to these functions may be defined in Scheme as follows.

(define cb-init
  (foreign-procedure "cb_init" () void))
(define register-callback
  (foreign-procedure "register_callback" (char integer-32) void))
(define event-loop
  (foreign-procedure "event_loop" () void))

A callback for selected characters can then be defined.

(define callback
  (lambda (p)
    (let ([code (foreign-callable p (char) void)])
      (lock-object code)
      (foreign-callable-entry-point code))))
(define ouch
  (callback
    (lambda (c)
      (printf "Ouch! Hit by '~c'~%" c))))
(define rats
  (callback
    (lambda (c)
      (printf "Rats! Received '~c'~%" c))))

(cb-init)
(register-callback #\a ouch)
(register-callback #\c rats)
(register-callback #\e ouch)

This sets up the following interaction.

> (event-loop)
a
Ouch! Hit by 'a'
b
c
Rats! Received 'c'
d
e
Ouch! Hit by 'e'

A more well-behaved version of this example would save each code object returned by foreign-callable and unlock it when it is no longer registered as a callback.


procedure: (foreign-callable-entry-point code)
returns: the address of the foreign-callable entry point in code

code must be a code object produced by foreign-callable.

Section 4.6. C Library Routines

Additional foreign interface support is provided via a set of C preprocessor macros and C-callable library functions. Some of these routines allow C programs to examine, allocate, and alter Scheme objects. Others permit C functions to call Scheme procedures via a more primitive interface than that defined in Section 4.5. Still others permit the development of custom executable images and use of the Scheme system as a subordinate program within another program, e.g., for use as an extension language.

C code that uses these routines must include the "scheme.h" header file distributed with Chez Scheme and must be linked (statically or dynamically) with the Chez Scheme kernel. The header file contains definitions for the preprocessor macros and extern declarations for the library functions. The file is customized to the release of Chez Scheme and machine type with which it is distributed; it should be left unmodified to facilitate switching among Chez Scheme releases, and the proper version of the header file should always be used with C code compiled for use with a particular version of Chez Scheme.

The name of each routine begins with a capital S, e.g., Sfixnump. Many of the names are simple translations of the names of closely related Scheme procedures, e.g., Sstring_to_symbol is the C interface equivalent of string->symbol. Most externally visible entries in the Chez Scheme executable that are not documented here begin with capital S followed by an underscore (S_); their use should be avoided.

In addition to the various macros and external declarations given in scheme.h, the header file also defines (typedefs) three types used in the header file:

These types may vary depending upon the platform, although ptr is typically void *, iptr is typically long int, and uptr is typically unsigned long int.

Under Windows, defining SCHEME_IMPORT before including scheme.h causes scheme.h to declare its entry points using extern declspec (dllimport) rather than extern declspec (dllexport) (the default). Not defining SCHEME_IMPORT and instead defining SCHEME_STATIC causes scheme.h to declare exports using just extern. The static libraries distributed with Chez Scheme are built using SCHEME_STATIC.

The remainder of this section describes each of the C interface routines in turn. A declaration for each routine is given in ANSI C function prototype notation to precisely specify the argument and result types. Scheme objects have the C type ptr, which is defined in "scheme.h". Where appropriate, C values are accepted as arguments or returned as values in place of Scheme objects.

The preprocessor macros may evaluate their arguments more than once (or not at all), so care should be taken to ensure that this does not cause problems.

Customization.  The functions described here are used to initialize the Scheme system, build the Scheme heap, and run the Scheme system from a separate program.

[func] void Sscheme_init(void (*abnormal_exit)(void))
[func] void Sset_verbose(int v)
[func] void Sregister_boot_file(const char *name)
[func] void Sregister_heap_file(const char *name)
[func] void Sbuild_heap(const char *exec, void (*custom_init)(void))
[func] void Senable_expeditor(const char *historyfile)
[func] int Sscheme_start(int argc, char *argv[])
[func] int Sscheme_script(char *scriptfile, int argc, char *argv[])
[func] void Scompact_heap(void)
[func] void Ssave_heap(const char *path, int level)
[func] void Sscheme_deinit(void)

Sscheme_init causes the Scheme system to initialize its static memory in preparation for boot and heap registration. The abnormal_exit parameter should be a (possibly null) pointer to a C function of no arguments that takes appropriate action if the initialization or subsequent heap-building process fails. If null, the default action is to call exit(1).

Sset_verbose sets verbose mode on for nonzero values of v and off when v is zero. In verbose mode, the system displays a trace of the search process for subsequently registered boot and heap files.

Sregister_boot_file and Sregister_heap_file search for the named boot or heap file and register it for loading. The file is opened but not loaded until the heap is built via Sbuild_heap. For the first boot or heap file registered only, the system also searches for the boot or heap files upon which the named file depends, either directly or indirectly.

Sbuild_heap creates the Scheme heap from the registered boot and heap files. exec is assumed to be the name of or path to the executable image and is used when no boot or heap files have been registered as the base name for the boot and heap search process. exec may be null only if one or more boot or heap files have been registered. custom_init must be a (possibly null) pointer to a C function of no arguments; if non-null, it is called before any boot files are loaded but only if no heap files are found.

Sscheme_start invokes the interactive startup procedure, i.e., the value of the parameter scheme-start, with one Scheme string argument for the first argc elements of argv. Sscheme_script similarly invokes the script startup procedure, i.e., the value of the parameter scheme-script, with one Scheme string argument for scriptfile and the first argc elements of argv.

Senable_expeditor enables the expression editor (Section 2.2, Chapter 13), which is disabled by default, and determines the history file from which it restores and to which it saves the history. This procedure must be called after the heap is built, or an error will result. It must also be called before Sscheme_start in order to be effective. If the historyfile argument is the null pointer, the history is not restored or saved. The preprocessor variable FEATURE_EXPEDITOR is defined in scheme.h if support for the expression editor has been compiled into the system.

Scompact_heap compacts the Scheme heap and places all objects currently in the heap into a static generation. Objects in the static generation are never collected. That is, they are never moved during collection and the storage used for them is never reclaimed even if they become inaccessible. Scompact_heap is called implicitly after any boot files have been loaded. It is also usually used before saving a heap via Ssave_heap.

Ssave_heap saves the Scheme heap in the file identified by path. The level argument determines the heap level along with the level n of the highest level heap loaded during the boot process. If level is -2 a level n heap is saved. If level is -1 a level n + 1 heap is saved. Otherwise, level must be a nonnegative and no greater than n + 1, and a level level heap is saved.

Sscheme_deinit closes any open files, tears down the Scheme heap, and puts the Scheme system in an uninitialized state.

Predicates.  The predicates described here correspond to the similarly named Scheme predicates. A trailing letter p, for "predicate," is used in place of the question mark that customarily appears at the end of a Scheme predicate name. Each predicate accepts a single Scheme object and returns a boolean (C integer) value.

[macro] int Sfixnump(ptr obj)
[macro] int Scharp(ptr obj)
[macro] int Snullp(ptr obj)
[macro] int Seof_objectp(ptr obj)
[macro] int Sbwp_objectp(ptr obj)
[macro] int Sbooleanp(ptr obj)
[macro] int Spairp(ptr obj)
[macro] int Ssymbolp(ptr obj)
[macro] int Sprocedurep(ptr obj)
[macro] int Sflonump(ptr obj)
[macro] int Svectorp(ptr obj)
[macro] int Sfxvectorp(ptr obj)
[macro] int Sstringp(ptr obj)
[macro] int Sbignump(ptr obj)
[macro] int Sboxp(ptr obj)
[macro] int Sinexactnump(ptr obj)
[macro] int Sexactnump(ptr obj)
[macro] int Sratnump(ptr obj)
[macro] int Sinputportp(ptr obj)
[macro] int Soutputportp(ptr obj)
[macro] int Srecordp(ptr obj)

Accessors.  Some of the accessors described here correspond to similarly named Scheme procedures, while others are unique to this interface. Sfixnum_value, Schar_value, Sboolean_value, and Sflonum_value return the C equivalents of the given Scheme value.

[macro] iptr Sfixnum_value(ptr fixnum)
[macro] uptr Schar_value(ptr character)
[macro] int Sboolean_value(ptr obj)
[macro] double Sflonum_value(ptr flonum)

Sinteger_value and Sunsigned_value are similar to Sfixnum_value, except they accept not only fixnum arguments but bignum arguments in the range of C integer or unsigned values. Sinteger_value and Sunsigned_value accept the same range of Scheme integer values. They differ only in the result type, and so allow differing interpretations of negative and large unsigned values.

[func] iptr Sinteger_value(ptr integer)
[macro] uptr Sunsigned_value(ptr integer)

Scar, Scdr, Ssymbol_to_string (corresponding to symbol->string), and Sunbox are identical to their Scheme counterparts.

[macro] ptr Scar(ptr pair)
[macro] ptr Scdr(ptr pair)
[macro] ptr Ssymbol_to_string(ptr sym)
[macro] ptr Sunbox(ptr box)

Sstring_length, Svector_length, and Sfxvector_length each return a C integer representing the length (in elements) of the vector or string.

[macro] iptr Sstring_length(ptr str)
[macro] iptr Svector_length(ptr vec)
[macro] iptr Sfxvector_length(ptr fxvec)

Sstring_ref, Svector_ref, and Sfxvector_ref correspond to their Scheme counterparts, except that the index arguments are C integers and the return value for Sstring_ref is a C character.

[macro] char Sstring_ref(ptr str, iptr i)
[macro] ptr Svector_ref(ptr vec, iptr i)
[macro] ptr Sfxvector_ref(ptr fxvec, iptr i)

A Scheme string is represented as a length field followed by a sequence of character values. The sequence is always followed by a null byte for convenience in interfacing with C. The trailing null byte is not counted in determining the length of the string. (Other null bytes may be contained within the Scheme string as well.) Sstring_value returns a pointer to the start of the sequence of characters. Extreme care should be taken to discard the pointer returned by Sstring_value or to lock the string into memory (see Slock_object below) before any Scheme code is executed, whether by calling into Scheme or returning to a Scheme caller. The storage manager may otherwise relocate the object into which the pointer points and may copy other data over the object.

[macro] char * Sstring_value(ptr str)

Mutators.  Changes to mutable objects that contain pointers, such as pairs and vectors, must be tracked on behalf of the storage manager, as described in one of the references [13]. The operations described here perform this tracking automatically where necessary.

[func] void Sset_box(ptr box, ptr obj)
[func] void Sset_car(ptr pair, ptr obj)
[func] void Sset_cdr(ptr pair, ptr obj)
[macro] void Sstring_set(ptr str, iptr i, char c)
[func] void Svector_set(ptr vec, iptr i, ptr obj)
[func] void Sfxvector_set(ptr fxvec, iptr i, ptr fixnum)

Some Scheme objects, such as procedures and numbers, are not mutable, so no operators are provided for altering the contents of those objects.

Constructors.  The constructors described here create Scheme objects. Some objects, such as fixnums and the empty list, are represented as immediate values that do not require any heap allocation; others, such as pairs and vectors, are represented as pointers to heap allocated objects.

Snil, Strue, Sfalse, Sbwp_object, Seof_object, and Svoid construct constant immediate values representing the empty list ( () ), the boolean values (#t and #f), the broken-weak-pointer object (#!bwp), the eof object (#!eof), and the void object.

[macro] ptr Snil
[macro] ptr Strue
[macro] ptr Sfalse
[macro] ptr Sbwp_object
[macro] ptr Seof_object
[macro] ptr Svoid

Fixnums, characters, booleans, flonums, and strings may be created from their C equivalents.

[macro] ptr Sfixnum(iptr n)
[macro] ptr Schar(char c)
[macro] ptr Sboolean(int b)
[func] ptr Sflonum(double x)
[func] ptr Sstring(const char *s)
[func] ptr Sstring_of_length(const char *s, iptr n)

Sstring creates a Scheme copy of the C string s, while Sstring_of_length creates a Scheme string of length n and copies the first n bytes from s into the new Scheme string.

It is possible to determine whether a C integer is within fixnum range by comparing the fixnum value of a fixnum created from a C integer with the C integer:

#define fixnum_rangep(x) (Sfixnum_value(Sfixnum(x)) == x)

Sinteger and Sunsigned may be used to create Scheme integers whether they are in fixnum range or not.

[func] ptr Sinteger(iptr n)
[func] ptr Sunsigned(uptr n)

Sinteger and Sunsigned differ in their treatment of negative C integer values as well as C unsigned integer values that would appear negative if cast to integers. Sinteger converts such values into negative Scheme values, whereas Sunsigned converts such values into the appropriate positive Scheme values. For example, assuming a 32-bit, two's complement representation for iptrs, Sinteger(-1) and Sunsigned((iptr)0xffffffff) both evaluate to the Scheme integer -1, whereas Sunsigned(0xffffffff) and Sunsigned((uptr)-1) both evaluate to the Scheme integer #xffffffff (4294967295).

Whichever routine is used, Sinteger_value and Sunsigned_value always reproduce the corresponding C input value, thus the following are all equivalent to x if x is an iptr.

Sinteger_value(Sinteger(x))
(iptr)Sunsigned_value(Sinteger(x))
Sinteger_value(Sunsigned((uptr)x))
(iptr)Sunsigned_value(Sunsigned((uptr)x))

Similarly, the following are all equivalent to x if x is a uptr.

(uptr)Sinteger_value(Sinteger((iptr)x))
Sunsigned_value(Sinteger((iptr)x))
(uptr)Sinteger_value(Sunsigned(x))
Sunsigned_value(Sunsigned(x))

Scons and Sbox are identical to their Scheme counterparts.

[func] ptr Scons(ptr obj1, ptr obj2)
[func] ptr Sbox(ptr obj)

Sstring_to_symbol is similar to its Scheme counterpart, string->symbol, except that it takes a C string (character pointer) as input.

[func] ptr Sstring_to_symbol(const char *s)

Smake_string, Smake_vector, and Smake_fxvector are similar to their Scheme counterparts.

[func] ptr Smake_string(iptr n, int c)
[func] ptr Smake_vector(iptr n, ptr obj)
[func] ptr Smake_fxvector(iptr n, ptr fixnum)

Smake_uninitialized_string is similar to the one-argument make-string.

[func] ptr Smake_uninitialized_string(iptr n)

Accessing top-level values.  Top-level variable bindings may be accessed or assigned via Stop_level_value and Sset_top_level_value.

[func] ptr Stop_level_value(ptr sym)
[func] void Sset_top_level_value(ptr sym, ptr obj)

These procedures give fast access to the bindings in the original interaction environment and do not reflect changes to the interaction-environment parameter or top-level module imports. To access the current interaction-environment binding for a symbol, it is necessary to call the Scheme top-level-value and set-top-level-value! procedures instead.

Locking Scheme objects.  The storage manager periodically relocates objects in order to reclaim storage and compact the heap. This relocation is completely transparent to Scheme programs, since all pointers to a relocated object are updated to refer to the new location of the object. The storage manager cannot, however, update Scheme pointers that reside outside of the Scheme heap.

As a general rule, all pointers from C variables or data structures to Scheme objects should be discarded before entry (or reentry) into Scheme. That is, if a C procedure receives an object from Scheme or obtains it via the mechanisms described in this section, all pointers to the object should be considered invalid once the C procedure calls into Scheme or returns back to Scheme. Dereferencing an invalid pointer or passing it back to Scheme can have disastrous effects, including unrecoverable memory faults. The foregoing does not apply to immediate objects, e.g., fixnums, characters, booleans, or the empty list. It does apply to all heap allocated objects, including pairs, vectors, strings, all numbers other than fixnums, ports, procedures, and records.

In practice, the best way to ensure that C code does not retain pointers to Scheme objects is to immediately convert the Scheme objects into C equivalents, if possible. In certain cases, it is not possible to do so, yet retention of the Scheme object is essential to the design of the C portions of the program. In these cases, the object may be locked via the library routine Slock_object (or from Scheme, the equivalent procedure lock-object).

[func] void Slock_object(ptr obj)

Locking an object prevents the storage manager from reclaiming or relocating the object. Locking should be used sparingly, as it introduces memory fragmentation and increases storage management overhead. Locking can also lead to accidental retention of storage if objects are not unlocked. Objects may be unlocked via Sunlock_object (unlock-object).

Locking objects that have been made static via heap compaction (see Scompact_heap above) is unnecessary but harmless.

[func] void Sunlock_object(ptr obj)

An object may be locked more than once by successive calls to Slock_object or lock-object, in which case it must be unlocked by an equal number of calls to Sunlock_object or unlock-object before it is truly unlocked.

When a foreign procedure call is made into Scheme, a return address pointing into the Scheme code object associated with the foreign procedure is passed implicitly to the C routine. The system therefore locks the code object before calls are made from C back into Scheme and unlocks it upon return from Scheme. This locking is performed automatically; user code should never need to lock such code objects.

An object contained within a locked object, such as an object in the car of a locked pair, need not also be locked unless a separate C pointer to the object exists.

Registering foreign entry points.  Foreign entry points may be made visible to Scheme via Sforeign_symbol or Sregister_symbol.

[func] void Sforeign_symbol(const char *name, void *addr)
[func] void Sregister_symbol(const char *name, void *addr)

External entry points in object files or shared objects loaded as a result of a call to load-shared-object are automatically made visible by the system. Once a foreign entry point is made visible, it may be named in a foreign-procedure expression to create a Scheme-callable version of the entry point. Sforeign_symbol and Sregister_symbol allow programs to register nonexternal entry points, entry points in code linked statically with Chez Scheme, and entry points into code loaded directly from C, i.e., without load-shared-object. Sforeign_symbol and Sregister_symbol differ only in that Sforeign_symbol signals an error when an attempt is made to register an existing name, whereas Sregister_symbol permits existing names to be redefined.

Obtaining Scheme entry points.  Sforeign_callable_entry_point extracts the entry point from a code object produced by foreign-callable, performing the same operation as its Scheme counterpart, i.e., the Scheme procedure foreign-callable-entry-point.

[func] (void (*) (void)) Sforeign_callable_entry_point(ptr code)

This can be used to avoid converting the code object into an address until just when it is needed, which may eliminate the need to lock the code object in some circumstances, assuming that the code object is not saved across any calls back into Scheme.

Low-level support for calls into Scheme.  Support for calling Scheme procedures from C is provided by the set of routines documented below. Calling a Scheme procedure that expects a small number of arguments (0-3) involves the use of one of the following routines.

[func] ptr Scall0(ptr proc)
[func] ptr Scall1(ptr proc, ptr obj1)
[func] ptr Scall2(ptr proc, ptr obj1, ptr obj2)
[func] ptr Scall3(ptr proc, ptr obj1, ptr obj2, ptr obj3)

In each case, the first argument, proc, must be a Scheme procedure. The remaining arguments, which must be Scheme objects, are passed to the procedure. The tools described earlier in this section may be used to convert C datatypes into their Scheme equivalents. A program that automatically generates conversion code from declarations that are similar to foreign-procedure expressions is distributed with Chez Scheme. It can be found in the Scheme library directory on most systems in the file "foreign.ss".

A Scheme procedure may be obtained in a number of ways. For example, it may be received as an argument in a call from Scheme into C, obtained via another call to Scheme, extracted from a Scheme data structure, or obtained from the top-level environment via Stop_level_value.

A more general interface involving the following routines is available for longer argument lists.

[func] void Sinitframe(iptr n)
[func] void Sput_arg(iptr i, ptr obj)
[func] ptr Scall(ptr proc, iptr n)

A C procedure first calls Sinitframe with one argument, the number of arguments to be passed to Scheme. It then calls Sput_arg once for each argument (in any order), passing Sput_arg the argument number (starting with 1) and the argument. Finally, it calls Scall to perform the call, passing it the Scheme procedure and the number of arguments (the same number as in the call to Sinitframe). Programmers should ensure a Scheme call initiated via Sinitframe is completed via Scall before any other calls to Scheme are made and before a return to Scheme is attempted. If for any reason the call is not completed after Sinitframe has been called, it may not be possible to return to Scheme.

The following examples serve to illustrate both the simpler and more general interfaces.

/* a particularly silly way to multiply two floating-point numbers */
double mul(double x, double y) {
    ptr times = Stop_level_value(Sstring_to_symbol("*"));

    return Sflonum_value(Scall2(times, Sflonum(x), Sflonum(y)));
}

/* an equally silly way to call printf with five arguments */

/* it is best to define interfaces such as the one below to handle
 * calls into Scheme to prevent accidental attempts to nest frame
 * creation and to help ensure that initiated calls are completed
 * as discussed above.  Specialized versions tailored to particular
 * C argument types may be defined as well, with embedded conversions
 * to Scheme objects. */
ptr Scall5(ptr p, ptr x1, ptr x2, ptr x3, ptr x4, ptr x5) {
    Sinitframe(5);
    Sput_arg(1, x1);
    Sput_arg(2, x2);
    Sput_arg(3, x3);
    Sput_arg(4, x4);
    Sput_arg(5, x5);
    Scall(p, 5);
}

static void dumpem(char *s, int a, double b, ptr c, char *d) {
    printf(s, a, b, c, d);
}

static void foo(int x, double y, ptr z, char *s) {
    ptr ois, sip, read, expr, eval, c_dumpem;
    char *sexpr = "(foreign-procedure \"dumpem\" (string integer-32\
 double-float scheme-object string) void)";

  /* this series of statements is carefully crafted to avoid referencing
     variables holding Scheme objects after calls into Scheme */
    ois = Stop_level_value(Sstring_to_symbol("open-input-string"));
    sip = Scall1(ois, Sstring(sexpr));
    read = Stop_level_value(Sstring_to_symbol("read"));
    expr = Scall1(read, sip);
    eval = Stop_level_value(Sstring_to_symbol("eval"));
    Sforeign_symbol("dumpem", (void *)dumpem);
    c_dumpem = Scall1(eval, expr);
    Scall5(c_dumpem,
           Sstring("x = %d, y = %g, z = %x, s = %s\n"),
           Sinteger(x),
           Sflonum(y),
           z,
           Sstring(s));
}

Calls from C to Scheme should not be made from C interrupt handlers. When Scheme calls into C, the system saves the contents of certain dedicated machine registers in a register save area. When C then calls into Scheme, the registers are restored from the register save area. Because an interrupt can occur at any point in a computation, the contents of the register save locations would typically contain invalid information that would cause the Scheme system to fail to operate properly.

Activating, deactivating, and destroying threads.  Three functions are provided by the threaded versions of Scheme to allow C code to notify Scheme when a thread should be activated, deactivated, or destroyed.

[func] int Sactivate_thread(void)
[func] void Sdeactivate_thread(void)
[func] int Sdestroy_thread(void)

A thread created via the Scheme procedure fork-thread starts in the active state and need not be activated. Any thread that has been deactivated, and any thread created by some mechanism other than fork-thread must, however, be activated before before it can access Scheme data or execute Scheme code. Sactivate_thread is used for this purpose. It returns 1 the first time the thread is activated and 0 on each subsequent call.

Since active threads operating in C code prevent the storage management system from garbage collecting, a thread should be deactivated via Sdeactivate_thread whenever it may spend a significant amount of time in C code. This is especially important whenever the thread calls a C library function, like read, that may block indefinitely. Once deactivated, the thread must not touch any Scheme data or execute any Scheme code until it is reactivated, with one exception. The exception is that the thread may access or even modify a locked Scheme object, such as a locked string, that contains no pointers to other, unlocked Scheme objects. (Objects that are not locked may be relocated by the garbage collector while the thread is inactive.)

Sdestroy_thread is used to notify the Scheme system that the thread is shut down and any thread-specific data can be released.

Section 4.7. Continuations and Foreign Calls

foreign-callable and foreign-procedure allow arbitrary nesting of foreign and Scheme calls. Because other languages do not support the fully general first-class continuations of Scheme, the interaction between continuations and nested calls among Scheme and foreign procedures is problematic. Chez Scheme handles this interaction in a general manner by trapping attempts to return to stale foreign contexts rather than by restricting the use of continuations directly. A foreign context is a foreign frame and return point corresponding to a particular call from a foreign language, e.g., C, into Scheme. A foreign context becomes stale after a normal return to the context or after a return to some other foreign context beneath it on the control stack.

As a result of this treatment, Scheme continuations may be used to throw control either upwards or downwards logically through any mix of Scheme and foreign frames. Furthermore, until some return to a foreign context is actually performed, all return points remain valid. In particular, this means that programs that use continuations exclusively for nonlocal exits never attempt to return to a stale foreign context. (Nonlocal exits themselves are no problem and are implemented by the C library function longjmp or the equivalent.) Programs that use continuations more generally also function properly as long as they never actually return to a stale foreign context, even if control logically moves past stale foreign contexts via invocation of continuations.

Section 4.8. Example: Socket Operations

This section presents a simple socket interface that employs a combination of Scheme and C code. The C code defines a set of convenient low-level operating-system interfaces that can be used in the higher-level Scheme code to open, close, read from, and write to sockets.

The C code (csocket.c) is given below, followed by the Scheme code (socket.ss). The code should require little or no modification to run on most Unix systems and can be modified to work under Windows (using the Windows WinSock interface).

A sample session demonstrating the socket interface follows the code. See Section 9.12 for an example that demonstrates how to use the same socket interface to build a process port that allows transparent input from and output to a subprocess via a Scheme port.

C code.  

/* csocket.c */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <stdio.h>

/* c_write attempts to write the entire buffer, pushing through
   interrupts, socket delays, and partial-buffer writes */
int c_write(int fd, char *buf, unsigned n) {
    unsigned i, m;

    m = n;
    while (m > 0) {
        if ((i = write(fd, buf, m)) < 0) {
            if (errno != EAGAIN && errno != EINTR)
                return i;
        } else {
            m -= i;
            buf += i;
        }
    }
    return n;
}

/* c_read pushes through interrupts and socket delays */
int c_read(int fd, char *buf, unsigned n) {
    int i;

    for (;;) {
        i = read(fd, buf, n);
        if (i >= 0) return i;
        if (errno != EAGAIN && errno != EINTR) return -1;
    }
}

/* bytes_ready(fd) returns true if there are bytes available
   to be read from the socket identified by fd */
int bytes_ready(int fd) {
    int n;

    (void) ioctl(fd, FIONREAD, &n);
    return n;
}

/* socket support */

/* do_socket() creates a new AF_UNIX socket */
int do_socket(void) {

    return socket(AF_UNIX, SOCK_STREAM, 0);
}

/* do_bind(s, name) binds name to the socket s */
int do_bind(int s, char *name) {
    struct sockaddr_un sun;
    int length;

    sun.sun_family = AF_UNIX;
    (void) strcpy(sun.sun_path, name);
    length = sizeof(sun.sun_family) + sizeof(sun.sun_path);

    return bind(s, &sun, length);
}

/* do_accept accepts a connection on socket s */
int do_accept(int s) {
    struct sockaddr_un sun;
    int length;

    length = sizeof(sun.sun_family) + sizeof(sun.sun_path);

    return accept(s, &sun, &length);
}

/* do_connect initiates a socket connection */
int do_connect(int s, char *name) {
    struct sockaddr_un sun;
    int length;

    sun.sun_family = AF_UNIX;
    (void) strcpy(sun.sun_path, name);
    length = sizeof(sun.sun_family) + sizeof(sun.sun_path);

    return connect(s, &sun, length);
}

/* get_error returns the operating system's error status */
char* get_error(void) {
    extern int errno;
    return strerror(errno);
}

Scheme code.  

;;; socket.ss

;;; Requires csocket.so, built from csocket.c.
(case (machine-type)
  [(i3le ti3le i3fb ti3fb) (load-shared-object "libc.so.6")]
  [(ppcosx tppcosx i3osx ti3osx) (load-shared-object "libc.dylib")]
  [else (load-shared-object "libc.so")])

;;; Requires from C library:
;;;   close, dup, execl, fork, kill, listen, tmpnam, unlink
(load-shared-object "libc.so")

;;; basic C-library stuff

(define close
  (foreign-procedure "close" (integer-32)
    integer-32))

(define dup
  (foreign-procedure "dup" (integer-32)
    integer-32))

(define execl4
  (let ((execl-help
         (foreign-procedure "execl"
           (string string string string integer-32)
           integer-32)))
    (lambda (s1 s2 s3 s4)
      (execl-help s1 s2 s3 s4 0))))

(define fork
  (foreign-procedure "fork" ()
    integer-32))

(define kill
  (foreign-procedure "kill" (integer-32 integer-32)
    integer-32))

(define listen
  (foreign-procedure "listen" (integer-32 integer-32)
    integer-32))

(define tmpnam
  (foreign-procedure "tmpnam" (integer-32)
    string))

(define unlink
  (foreign-procedure "unlink" (string)
    integer-32))

;;; routines defined in csocket.c

(define accept
  (foreign-procedure "do_accept" (integer-32)
    integer-32))

(define bytes-ready?
  (foreign-procedure "bytes_ready" (integer-32)
    boolean))

(define bind
  (foreign-procedure "do_bind" (integer-32 string)
    integer-32))

(define c-error
  (foreign-procedure "get_error" ()
    string))

(define c-read
  (foreign-procedure "c_read" (integer-32 string integer-32)
    integer-32))

(define c-write
  (foreign-procedure "c_write" (integer-32 string integer-32)
    integer-32))

(define connect
  (foreign-procedure "do_connect" (integer-32 string)
    integer-32))

(define socket
  (foreign-procedure "do_socket" ()
    integer-32))

;;; higher-level routines

(define dodup
 ; (dodup old new) closes old and dups new, then checks to
 ; make sure that resulting fd is the same as old
  (lambda (old new)
    (check 'close (close old))
    (unless (= (dup new) old)
      (error 'dodup
        "couldn't set up child process io for fd ~s" old))))

(define dofork
 ; (dofork child parent) forks a child process and invokes child
 ; without arguments and parent with the child's pid
  (lambda (child parent)
    (let ([pid (fork)])
      (cond
        [(= pid 0) (child)]
        [(> pid 0) (parent pid)]
        [else (error 'fork (c-error))]))))

(define setup-server-socket
 ; create a socket, bind it to name, and listen for connections
  (lambda (name)
    (let ([sock (check 'socket (socket))])
      (unlink name)
      (check 'bind (bind sock name))
      (check 'listen (listen sock 1))
      sock)))

(define setup-client-socket
 ; create a socket and attempt to connect to server
  (lambda (name)
    (let ([sock (check 'socket (socket))])
      (check 'connect (connect sock name))
      sock)))

(define accept-socket
 ; accept a connection
  (lambda (sock)
    (check 'accept (accept sock))))

(define check
 ; signal an error if status x is negative, using c-error to
 ; obtain the operating-system's error message
  (lambda (who x)
    (if (< x 0)
        (error who (c-error))
        x)))

(define terminate-process
 ; kill the process identified by pid
  (lambda (pid)
    (define sigterm 15)
    (kill pid sigterm)
    (void)))

Sample session.  

> (define client-pid)
> (define client-socket)
> (let* ([server-socket-name (tmpnam 0)]
         [server-socket (setup-server-socket server-socket-name)])
   ; fork a child, use it to exec a client Scheme process, and set
   ; up server-side client-pid and client-socket variables.
    (dofork   ; child
      (lambda () 
       ; the child establishes the socket input/output fds as
       ; stdin and stdout, then starts a new Scheme session
        (check 'close (close server-socket))
        (let ([sock (setup-client-socket server-socket-name)])
          (dodup 0 sock)
          (dodup 1 sock))
        (check 'execl (execl4 "/bin/sh" "/bin/sh" "-c" "exec scheme"))
        (error 'client "returned!"))
      (lambda (pid) ; parent
       ; the parent waits for a connection from the client
        (set! client-pid pid)
        (set! client-socket (accept-socket server-socket))
        (check 'close (close server-socket)))))
> (define put ; procedure to send data to client
    (lambda (x)
      (let ([s (format "~s~%" x)])
        (c-write client-socket s (string-length s)))
      (void)))
> (define get ; procedure to read data from client
    (let ([buff (make-string 1024)])
      (lambda ()
        (let ([n (c-read client-socket buff (string-length buff))])
          (printf "client:~%~a~%server:~%" (substring buff 0 n))))))
> (get)
client:
Chez Scheme Version 7.0
Copyright (c) 1985-2005 Cadence Research Systems

>
server:
> (put '(let ((x 3)) x))
> (get)
client:
3
>
server:
> (terminate-process client-pid)
> (exit)

R. Kent Dybvig / Chez Scheme Version 7 User's Guide
Copyright © 2005 R. Kent Dybvig
Revised July 2007 for Chez Scheme Version 7.4
Cadence Research Systems / www.scheme.com
Cover illustration © 1998 Jean-Pierre Hébert
ISBN: 0-9667139-1-5
to order this book / about this book