This manual describes MzScheme's C interface, which allows the interpreter to be extended by a dynamically-loaded library, or embedded within an abitrary C/C++ program. The manual assumes familiarity with MzScheme, as described in PLT MzScheme: Language Manual.
To write a C/C++-based extension for MzScheme, follow these steps:
For each C/C++ file that uses MzScheme library functions, #include the file escheme.h.
This file is distributed with the PLT software in plt/include, but if mzc is used to compile, this path is found automatically.
Define the C function scheme_initialize, which takes a Scheme_Env * namespace (see section 4) and returns a Scheme_Object * Scheme value.
This initialization function can install new global primitive
procedures or other values into the namespace, or it can simply
return a Scheme value. The initialization function is called when the
extension is loaded with load-extension (the first time);
the return value from scheme_initialize is used as the return
value for load-extension. The namespace provided to
scheme_initialize is the current namespace when
load-extension is called.
Define the C function scheme_reload, which has the same arguments and return type as scheme_initialize.
This function is called if load-extension is called a second
time (or more times) for an extension. Like scheme_initialize,
the return value from this function is the return value for
load-extension.
Define the C function scheme_module_name, which takes no arguments and returns a Scheme_Object * value, either a symbol or scheme_false.
The function should return a symbol when the effect of calling
scheme_initialize and scheme_reload is only to declare
a module with the returned name. This function is called when the
extension is loaded to satisfy a require declaration.
The scheme_module_name function may be called before scheme_initialize and scheme_reload, after those functions, or both before and after, depending on how the extension is loaded and re-loaded.
Compile the extension C/C++ files to create platform-specific object files.
The mzc compiler, distributed with MzScheme, compiles plain C files when the --cc flag is specified. More precisely, mzc does not compile the files itself, but it locates a C compiler on the system and launches it with the appropriate compilation flags. If the platform is a relatively standard Unix system, a Windows system with either Microsoft's C compiler or gcc in the path, or a Mac OS system with Metrowerks CodeWarrior installed, then using mzc is typically easier than working with the C compiler directly.
Link the extension C/C++ files with mzdyn.o (Unix) or mzdyn.obj (Windows) to create a shared object.
The mzdyn object file is distributed in plt/lib for Unix or Windows, but it is not distributed for Mac OS. For Windows, the object file is in a compiler-specific sub-directory.
The mzc compiler links object files into an extension when the --ld flag is specified, automatically locating mzdyn. Under Mac OS, mzc generates the mzdyn object file as necessary.
Load the shared object within Scheme using
(load-extension , where path)path is the name of
the extension file generated in the previous step.
IMPORTANT: Scheme values are garbage collected using a conservative garbage collector, so pointers to MzScheme objects can be kept in registers, stack variables, or structures allocated with scheme_malloc. However, static variables that contain pointers to collectable memory must be registered using scheme_register_extension_global (see section 3).
As an example, the following C code defines an extension that returns
"hello world" when it is loaded:
#include "escheme.h"
Scheme_Object *scheme_initialize(Scheme_Env *env) {
return scheme_make_string("hello world");
}
Scheme_Object *scheme_reload(Scheme_Env *env) {
return scheme_initialize(env); /* Nothing special for reload */
}
Scheme_Object *scheme_module_name() {
return scheme_false;
}
Assuming that this code is in the file hw.c, the extension is compiled under Unix with the following two commands:
mzc --cc hw.c
mzc --ld hw.so hw.o
(Note that the --cc and --ld flags are each prefixed by two dashes, not one.)
The plt/collects/mzscheme/examples directory in the PLT distribution contains additional examples.
To embed MzScheme in a program, first download the MzScheme source code. Then, follow these steps:
Compile the MzScheme libraries.
Under Unix, the libraries are libmzscheme.a and libgc.a. After compiling MzScheme and running make install, the libraries are in a platform-specific directory under plt/collects/mzscheme/lib/. Under Windows and Mac OS, consult the compilation instructions for information on compiling the libraries.
For each C/C++ file that uses MzScheme library functions, #include the file scheme.h.1
This file is distributed with the PLT software in plt/include.
In your main program, obtain a global MzScheme environment Scheme_Env * by calling scheme_basic_env. This function must be called before any other function in the MzScheme library (except scheme_make_param).
Access MzScheme though scheme_load, scheme_eval, and/or other top-level MzScheme functions described in this manual.
Compile the program and link it with the MzScheme libraries.
Scheme values are garbage collected using a conservative garbage collector, so pointers to MzScheme objects can be kept in registers, stack variables, or structures allocated with scheme_malloc. In an embedding application, static variables are also automatically registered as roots for garbage collection (but see the Windows-specific note below).
For example, the following is a simple embedding program which
evaluates all expressions provided on the command line and displays
the results, then runs a read-eval-print
loop:
#include "scheme.h"
int main(int argc, char *argv[])
{
Scheme_Env *e = scheme_basic_env();
Scheme_Object *curout = scheme_get_param(scheme_config, MZCONFIG_OUTPUT_PORT);
int i;
for (i = 1; i < argc; i++) {
if (scheme_setjmp(scheme_error_buf)) {
return -1; /* There was an error */
} else {
Scheme_Object *v = scheme_eval_string(argv[i], e);
scheme_display(v, curout);
scheme_display(scheme_make_character('\n'), curout);
/* read-eval-print loop, implicitly uses the initial Scheme_Env: */
scheme_apply(scheme_builtin_value("read-eval-print-loop"), 0, NULL);
}
}
return 0;
}
Under Windows, the garbage collector finds static variables in an embeddeding program by examining all memory pages. This strategy fails if a program contains multiple Windows threads; a page may get unmapped by a thread while the collector is examining the page, causing the collector to crash. To avoid this problem, call scheme_set_stack_base with a non-zero second argument before calling any scheme_ function, and register all globals with scheme_register_static.
MzScheme implements threads for Scheme programs without aid from the operating system. MzScheme can co-exist with additional OS-implemented threads that are created by an extension or an embedding program, but the additional OS threads must not call any scheme_ function. Only the OS thread that originally calls scheme_basic_env can call scheme_ functions.2
When scheme_basic_env is called a second time to reset the interpreter, it can be called in an OS thread that is different from the original call to scheme_basic_env. Thereafter, all calls to scheme_ functions must originate from the new thread.
See section 8 for more information about threads, including the possible effects of MzScheme's thread implementation on extension and embedding C code.
1 The C preprocessor symbol SCHEME_DIRECT_EMBEDDED is defined as 1 when scheme.h is #included, or as 0 when escheme.h is #included.
2 This restriction is stronger than saying all calls must be serialized across threads. MzScheme relies on properties of specific threads to avoid stack overflow and garbage collection.