Chapter 3. 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 3.1. The second is via static or dynamic loading and invocation of procedures written in C, which is discussed in Sections 3.2 through 3.6.

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 3.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 invoke flush-output-port on the process-output-port to force output to be sent immediately from Scheme to the subprocess, 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 3.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 3.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. 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. Only two other conventions are currently supported, and only under Windows: __stdcall and __cdecl. Use __stdcall to access most Windows API procedures. Use __cdecl for Windows API procedures with varargs procedures, for C library procedures, and for most other procedures. Since __cdecl is the default, specifying __cdecl is equivalent to specifying #f or no convention.

Complete type checking and conversion is provided for foreign parameter passing. The types scheme-object, string, and foreign-object (MIPS-based systems only) 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:

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

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

fixnum: Only fixnums are valid. They are converted to a two's complement 32-bit representation. Transmission of fixnums is slightly faster than transmission of integer-32 and unsigned-32 values.

integer-32: Only exact integers from to are valid. The integers are converted to a two's complement 32-bit representation.

unsigned-32: Only nonnegative exact integers from 0 to are valid. The integers are converted to an unsigned 32-bit representation.

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.

(foreign-object size alignment): (MIPS-based systems only.) If the parameter is a string (representing a foreign object), the foreign object of size size contained within the string is copied to the C stack, aligned as necessary on an even alignment-byte boundary. If the parameter is an integer (representing a foreign pointer), the foreign object to which the argument points is copied to the C stack, aligned as necessary on an even alignment-byte boundary.

foreign-pointer: (MIPS-based systems only.) If the parameter is a string (representing a foreign object), a pointer to the foreign object contained within the string is passed. If the parameter is an integer (representing a foreign pointer), the pointer itself is passed.

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 run-time errors 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.

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

char: The low-order byte of the result is converted to a Scheme character. The result must be in the range -128 to +255.

fixnum: The result is interpreted as a signed integer and is converted to a Scheme fixnum. The result must be in the fixnum range.

integer-32: The result is interpreted as a signed integer and is converted to a Scheme exact integer.

unsigned-32: The result is interpreted as an unsigned integer and is converted to a Scheme nonnegative exact integer.

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.

(foreign-object size alignment): (MIPS-based systems only.) A string representation of the foreign object returned from the foreign procedure is created. The object is assumed to be size bytes long and aligned on an alignment-byte boundary.

foreign-pointer: (MIPS-based systems only.) An integer representation of the foreign pointer returned from the foreign procedure is created.

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 3.3) it can be accessed as follows:

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

A foreign entry is not looked up when the code containing a foreign-procedure expression is loaded or compiled. Nor is the entry looked up each time a foreign procedure is called. Instead, a foreign entry is looked up when a foreign-procedure expression is evaluated. Thus, the following definition is always valid since the foreign-procedure expression is not immediatedly 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 lookup need only be done 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 3.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 3.3. Providing Access to Foreign Procedures

Foreign procedures can be provided in a variety of ways:

The last two options are available on all platforms upon which Chez Scheme currently runs. load-foreign and provide-foreign-entries is now mostly obsolete but is still supported on Sun Sparc systems running SunOS 4.X. load-shared-object is available on most other platforms, including Sun Sparc systems running Solaris 2.0 (SunOS 5.X) or later, DEC Alpha systems running Digital Unix 2.X or later, SGI systems running IRIX 5.X or later, PowerPC systems running AIX 4.1 or later, HP PA-RISC systems running HP/UX 9.X or later, Intel-based Linux systems running kernel version 2.X or higher, Intel-based Windows NT 3.51 or later, and Windows 95/98.


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")  #t
((foreign-procedure "strlen"
    (string) integer-32)
 "hey!")  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 AIX, load-shared-object is based on the system routine load. Under HPUX, load-shared-object is based on the system routine shl_load. 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.

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 Sun Sparc systems running Solaris 2.X or higher:

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

On DEC Alpha systems running Digital Unix 2.X or higher:

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

On SGI systems running IRIX 5.X:

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

On PA-RISC systems running HP/UX 9.X or later:

(system "cc -Ae +z -c even.c")
(system "cc -Ae +z -c odd.c")
(system "ld -b -o evenodd.so even.o odd.o")

On Intel-based Linux systems running kernel version 2.X or higher:

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

On Intel-based Windows NT 3.51 or later or Windows 95/98, we must 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")

On PowerPC systems running AIX 4.1 or higher, it is necessary to provide ".imp" and ".exp" files on the command line. (See the documentation for ld for details). The ".imp" file lists imports, and the ".exp" file lists exports. Since we do not have any imports, the ".imp" can be empty. The ".exp" file should simply contain a list of the exported identifiers, one per line. We also need to provide an entry point, so we include an extra file evenodd.c that defines one.

(system "echo > evenodd.imp")
(system "echo even > evenodd.exp; echo odd >> evenodd.exp")
(system "echo 'void init(void) { return; }' > evenodd.c")
(system "cc -c even.c")
(system "cc -c odd.c")
(system "cc -c evenodd.c")
(system "ld -o evenodd.so -e init -bI:evenodd.imp -bE:evenodd.exp\\
           evenodd.o even.o odd.o")

(The \\ in the last command should be omitted if the command is all on one line.)

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)))  (#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: (provide-foreign-entries entries)
procedure: (provide-foreign-entries entries libraries)
returns: unspecified

provide-foreign-entries expects an entry name or list of entry names along with an optional library name or list of library names. The entry and library names must be strings. The libraries, or the default libraries (see default-foreign-libraries) if no library argument is provided, are searched for the entry names. It is an error if any entries are unresolved. If an entry is already available no libraries are consulted. The library list is passed directly to the system linker without interpretation by the Scheme system.

This procedure is not supported on all machines and operating systems on which Chez Scheme runs.

(foreign-entry? "getenv")  #f
(provide-foreign-entries '("getenv"))
(foreign-entry? "getenv")  #t
(define getenv
  (foreign-procedure "getenv"
    (string) string))
(getenv "HOME")  "/home/elmer/fudd"
(getenv "home")  #f

The result of the last example is #f because the library routine getenv returns the null pointer if it fails to find a value for the string in the environment.


procedure: (load-foreign files)
procedure: (load-foreign files libraries)
returns: unspecified

load-foreign expects a file name or list of file names along with an optional library name or list of library names. The file and library names must be strings. The files must be compiled but not linked. All external symbols in the files, along with external symbols linked from the libraries, will be available as foreign entries. If no library argument is provided a default library list (see default-foreign-libraries) is used. Files are linked with entries from previous foreign loads and with entries from other files in the file list. It is an error if any references are unresolved after a call to load-foreign, so files that depend upon each other must be loaded together. It is also an error if an external symbol defined in one of the files conflicts with an existing external symbol in the base system or in a previously loaded file.

This procedure is not supported on all machines and operating systems on which Chez Scheme runs.

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 before they can be loaded:

cc -c odd.c
cc -c even.c

Now the ".o" files can be loaded into Scheme and the even and odd procedures can be provided as foreign procedures. Since the files reference each other they must be loaded together.

(load-foreign '("odd.o" "even.o"))
(let ([odd (foreign-procedure "odd"
             (integer-32) boolean)]
      [even (foreign-procedure "even"
              (integer-32) boolean)])
  (list (even 100) (odd 100)))  (#t #f)


parameter: default-foreign-libraries

When called with no arguments, default-foreign-libraries returns the list of libraries used by load-foreign and provide-foreign-entries when no default library argument is provided. When called with a list of strings, which should represent valid foreign libraries, the list replaces the current default foreign library list.


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. It may also be used to remove duplicate entries before a foreign file is loaded.

remove-foreign-entry cannot be used to remove an entry created as a result of a call to load-shared-object.

(foreign-entry? "strlen")  #t
(define strlen
  (foreign-procedure "strlen"
    (string) integer-32))
(remove-foreign-entry "strlen")
(foreign-entry? "strlen")  #f
(strlen "howdy")  5

Even though access to the "strlen" entry is not possible after it is removed, foreign code loaded before removal of the entry and foreign procedures created before removal of the entry are not affected. The entry point still exists; it is simply no longer available for linking from within Scheme or from foreign object files loaded after the entry has been removed. Consequently, to reload a foreign file one must remove the old entries before loading the file and recreate the foreign procedures that use the entries after the file is loaded.


Section 3.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")  #f
(foreign-entry? "=foo")  #t


Section 3.5. C Library Routines

Additional foreign interface support is provided via a set of C preprocessor macros and C-callable library functions. These routines allow C programs to examine, allocate, and alter Scheme objects. They also permit C functions to call Scheme procedures.

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 executable. This 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 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.

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.

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 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)

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] int Sfixnum_value(ptr fixnum)
[macro] char 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.

[function] int Sinteger_value(ptr integer)
[macro] unsigned 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)

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

[macro] int Svector_length(ptr vec)
[macro] int Sstring_length(ptr str)

Svector_ref and Sstring_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] ptr Svector_ref(ptr vec, int i)
[macro] char Sstring_ref(ptr str, int 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 [11]. The operations described here perform this tracking automatically where necessary.

[function] void Sset_box(ptr box, ptr obj)
[function] void Sset_car(ptr pair, ptr obj)
[function] void Sset_cdr(ptr pair, ptr obj)
[macro] void Sstring_set(ptr str, int i, char c)
[function] void Svector_set(ptr vec, int i, ptr obj)

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(int n)
[macro] ptr Schar(char c)
[macro] ptr Sboolean(int b)
[function] ptr Sflonum(double x)
[function] ptr Sstring(char *s)
[function] ptr Sstring_of_length(char *s, int 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.

[function] ptr Sinteger(int n)
[function] ptr Sunsigned(unsigned 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 C integers, Sinteger(-1) and Sunsigned((int)0xffffffff) both evaluate to the Scheme integer -1, whereas Sunsigned(0xffffffff) and Sunsigned((unsigned)-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 a C signed integer.

Sinteger_value(Sinteger(x))
(int)Sunsigned_value(Sinteger(x))
Sinteger_value(Sunsigned((unsigned)x))
(int)Sunsigned_value(Sunsigned((unsigned)x))

Similarly, the following are all equivalent to x if x is a C unsigned integer.

(unsigned)Sinteger_value(Sinteger((int)x))
Sunsigned_value(Sinteger((int)x))
(unsigned)Sinteger_value(Sunsigned(x))
Sunsigned_value(Sunsigned(x))

Scons and Sbox are identical to their Scheme counterparts.

[function] ptr Scons(ptr obj1, ptr obj2)
[function] 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.

[function] ptr Sstring_to_symbol(char *s)

Smake_vector and Smake_string are similar to their Scheme counterparts.

[function] ptr Smake_vector(int n, ptr obj)
[function] ptr Smake_string(int n, char c)

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

[function] ptr Smake_uninitialized_string(int n)

Accessing top-level values.  Top-level variable bindings may be accessed or assigned via Stop_level_value and Sset_top_level_value, which are identical to their Scheme counterparts.

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

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).

[function] 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).

[function] 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.

[function] void Sforeign_symbol(char *name, int addr)
[function] void Sregister_symbol(char *name, int addr)

Once a foreign entry point is registered, it may be named in a foreign-procedure expression to create a Scheme-callable version of the entry point. External entry points in object files or shared objects loaded as a result of a call to load-foreign or load-shared-object are automatically registered by the system. 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-foreign or 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.

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.

[function] ptr Scall0(ptr proc)
[function] ptr Scall1(ptr proc, ptr obj1)
[function] ptr Scall2(ptr proc, ptr obj1, ptr obj2)
[function] 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.

[function] void Sinitframe(int n)
[function] void Sput_arg(int i, ptr obj)
[function] ptr Scall(ptr proc, int 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);
}

void foo(int x, double y, ptr z, char *s) {
    ptr ois, sip, read, expr, eval, c_printf;
    char *sexpr = "(foreign-procedure \"printf\" (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("printf", (int)printf);
    c_printf = Scall1(eval, expr);
    Scall5(c_printf,
           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.


Section 3.6. Continuations and Foreign 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 (or above it, if one thinks of stacks as growing downward).

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. 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 3.7. 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 8.10 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.
(load-shared-object "./csocket.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 6.0
Copyright (c) 1998 Cadence Research Systems

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


Chez Scheme User's Guide
© 1998 R. Kent Dybvig
Cadence Research Systems
http://www.scheme.com
Illustrations © 1998 Jean-Pierre Hébert
about this book