9 Embedding into a Program
The Racket run-time system can be embedded into a larger program. The embedding process for Racket CGC or Racket 3m (see CGC versus 3m) is essentially the same, but the process for Racket 3m is most easily understood as a variant of the process for Racket CGC (even though Racket 3m is the standard variant of Racket).
9.1 CGC Embedding
To embed Racket CGC in a program, follow these steps:
Locate or build the Racket CGC libraries. Since the standard distribution provides 3m libraries, only, you will most likely have to build from source.
On Unix, the libraries are "libracket.a" and "libmzgc.a" (or "libracket.so" and "libmzgc.so" for a dynamic-library build, with "libracket.la" and "libmzgc.la" files for use with libtool). Building from source and installing places the libraries into the installation’s "lib" directory. Be sure to build the CGC variant, since the default is 3m.
On Windows, stub libraries for use with Microsoft tools are "libracketx.lib" and "libmzgcx.lib" (where x represents the version number) are in a compiler-specific directory in "racket\lib". These libraries identify the bindings that are provided by "libracketx.dll" and "libmzgcx.dll" —
which are typically installed in "racket\lib". When linking with Cygwin, link to "libracketx.dll" and "libmzgcx.dll" directly. At run time, either "libracketx.dll" and "libmzgcx.dll" must be moved to a location in the standard DLL search path, or your embedding application must “delayload” link the DLLs and explicitly load them before use. ("Racket.exe" and "GRacket.exe" use the latter strategy.) On Mac OS, dynamic libraries are provided by the "Racket" framework, which is typically installed in "lib" sub-directory of the installation. Supply -framework Racket to gcc when linking, along with -F and a path to the "lib" directory. Beware that CGC and 3m libraries are installed as different versions within a single framework, and installation marks one version or the other as the default (by setting symbolic links); install only CGC to simplify accessing the CGC version within the framework. At run time, either "Racket.framework" must be moved to a location in the standard framework search path, or your embedding executable must provide a specific path to the framework (possibly an executable-relative path using the Mach-O @executable_path prefix).
For each C file that uses Racket library functions, #include the file "scheme.h".
The C preprocessor symbol SCHEME_DIRECT_EMBEDDED is defined as 1 when "scheme.h" is #included, or as 0 when "escheme.h" is #included.
The "scheme.h" file is distributed with the Racket software in the installation’s "include" directory. Building and installing from source also places this file in the installation’s "include" directory.
Start your main program through the scheme_main_setup (or scheme_main_stack_setup) trampoline, and put all uses of Racket functions inside the function passed to scheme_main_setup. The scheme_main_setup function registers the current C stack location with the memory manager. It also creates the initial namespace Scheme_Env* by calling scheme_basic_env and passing the result to the function provided to scheme_main_setup. (The scheme_main_stack_setup trampoline registers the C stack with the memory manager without creating a namespace.)
On Windows, when support for parallelism is enabled in the Racket build (as is the default), then before calling scheme_main_setup, your embedding application must first call scheme_register_tls_space:
scheme_register_tls_space(&tls_space, 0);
where tls_space is declared as a thread-local pointer variable in the main executable (i.e., not in a dynamically linked DLL):
static __declspec(thread) void *tls_space;
Changed in version 6.3: Calling scheme_register_tls_space is required on all Windows variants, although the call may be a no-op, depending on how Racket is built.
Configure the namespace by adding module declarations. The initial namespace contains declarations only for a few primitive modules, such as '#%kernel, and no bindings are imported into the top-level environment.
To embed a module like racket/base (along with all its dependencies), use raco ctool --c-mods ‹dest›, which generates a C file ‹dest› that contains modules in bytecode form as encapsulated in a static array. The generated C file defines a declare_modules function that takes a Scheme_Env*, installs the modules into the environment, and it adjusts the module name resolver to access the embedded declarations. If embedded modules refer to runtime files that need to be carried along, supply --runtime to raco ctool --c-mods to collect the runtime files into a directory; see Embedding Modules via C for more information.
Alternatively, use scheme_set_collects_path and scheme_init_collection_paths to configure and install a path for finding modules at run time.
On Windows, raco ctool --c-mods ‹dest› --runtime ‹dest-dir› includes in ‹dest-dir› optional DLLs that are referenced by the Racket library to support extflonums and bytes-open-converter. Call scheme_set_dll_path to register ‹dest-dir› so that those DLLs can be found at run time.
Access Racket through scheme_dynamic_require, scheme_load, scheme_eval, and/or other functions described in this manual.
If the embedding program configures built-in parameters in a way that should be considered part of the default configuration, then call scheme_seal_parameters afterward. The snapshot of parameter values taken by scheme_seal_parameters is used for certain privileged operations, such as installing a PLaneT package.
Compile the program and link it with the Racket libraries.
With Racket CGC, Racket values are garbage collected using a conservative garbage collector, so pointers to Racket objects can be kept in registers, stack variables, or structures allocated with scheme_malloc. In an embedding application on some platforms, static variables are also automatically registered as roots for garbage collection (but see notes below specific to Mac OS and Windows).
For example, the following is a simple embedding program that runs a module "run.rkt", assuming that "run.c" is created as
raco ctool --c-mods run.c "run.rkt"
to generate "run.c", which encapsulates the compiled form of "run.rkt" and all of its transitive imports (so that they need not be found separately a run time).
"main.c"
#include "scheme.h"
#include "run.c"
static int run(Scheme_Env *e, int argc, char *argv[])
{
Scheme_Object *a[2];
/* Declare embedded modules in "run.c": */
declare_modules(e);
a[0] = scheme_make_pair(scheme_intern_symbol("quote"),
scheme_make_pair(scheme_intern_symbol("run"),
scheme_make_null()));
a[1] = scheme_false;
scheme_dynamic_require(2, a);
return 0;
}
int main(int argc, char *argv[])
{
return scheme_main_setup(1, run, argc, argv);
}
As another example, the following is a simple embedding program that evaluates all expressions provided on the command line and displays the results, then runs a read-eval-print loop, all using racket/base. Run
raco ctool --c-mods base.c ++lib racket/base
to generate "base.c", which encapsulates racket/base and all of its transitive imports.
"main.c"
#include "scheme.h"
#include "base.c"
static int run(Scheme_Env *e, int argc, char *argv[])
{
Scheme_Object *curout;
int i;
Scheme_Thread *th;
mz_jmp_buf * volatile save, fresh;
/* Declare embedded modules in "base.c": */
declare_modules(e);
scheme_namespace_require(scheme_intern_symbol("racket/base"));
curout = scheme_get_param(scheme_current_config(),
MZCONFIG_OUTPUT_PORT);
th = scheme_get_current_thread();
for (i = 1; i < argc; i++) {
save = th->error_buf;
th->error_buf = &fresh;
if (scheme_setjmp(*th->error_buf)) {
th->error_buf = save;
return -1; /* There was an error */
} else {
Scheme_Object *v, *a[2];
v = scheme_eval_string(argv[i], e);
scheme_display(v, curout);
scheme_display(scheme_make_char('\n'), curout);
/* read-eval-print loop, uses initial Scheme_Env: */
a[0] = scheme_intern_symbol("racket/base");
a[1] = scheme_intern_symbol("read-eval-print-loop");
scheme_apply(scheme_dynamic_require(2, a), 0, NULL);
th->error_buf = save;
}
}
return 0;
}
int main(int argc, char *argv[])
{
return scheme_main_setup(1, run, argc, argv);
}
If modules embedded in the executable need to access runtime files (via racket/runtime-path forms), supply the --runtime flag to raco ctool, specifying a directory where the runtime files are to be gathered. The modules in the generated ".c" file will then refer to the files in that directory; the directory is normally specified relative to the executable, but the embedding application must call scheme_set_exec_cmd to set the executable path (typically argv[0]) before declaring modules.
On Mac OS, or on Windows when Racket is compiled to a DLL using Cygwin, the garbage collector cannot find static variables automatically. In that case, scheme_main_setup must be called with a non-zero first argument.
On Windows (for any other build mode), the garbage collector finds static variables in an embedding 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_main_setup with a non-zero first argument.
When an embedding application calls scheme_main_setup with a non-zero first argument, it must register each of its static variables with MZ_REGISTER_STATIC if the variable can contain a GCable pointer. For example, if curout above is made static, then MZ_REGISTER_STATIC(curout) should be inserted before the call to scheme_get_param.
When building an embedded Racket CGC to use SenoraGC (SGC) instead of the default collector, scheme_main_setup must be called with a non-zero first argument. See Memory Allocation for more information.
9.2 3m Embedding
Racket 3m can be embedded mostly the same as Racket, as long as the embedding program cooperates with the precise garbage collector as described in Cooperating with 3m.
In either your source in the in compiler command line, #define MZ_PRECISE_GC before including "scheme.h". When using raco ctool with the --cc and --3m flags, MZ_PRECISE_GC is automatically defined.
In addition, some library details are different:
On Unix, the library is just "libracket3m.a" (or "libracket3m.so" for a dynamic-library build, with "libracket3m.la" for use with libtool). There is no separate library for 3m analogous to CGC’s "libmzgc.a".
On Windows, the stub library for use with Microsoft tools is "libracket3mx.lib" (where x represents the version number). This library identifies the bindings that are provided by "libracket3mx.dll". There is no separate library for 3m analogous to CGC’s "libmzgcx.lib".
On Mac OS, 3m dynamic libraries are provided by the "Racket" framework, just as for CGC, but as a version suffixed with "_3m".
For Racket 3m, an embedding application must call scheme_main_setup with a non-zero first argument.
The simple embedding programs from the previous section can be processed by raco ctool --xform, then compiled and linked with Racket 3m. Alternately, the source code can be extended to work with either CGC or 3m depending on whether MZ_PRECISE_GC is defined on the compiler’s command line:
"main.c"
#include "scheme.h"
#include "run.c"
static int run(Scheme_Env *e, int argc, char *argv[])
{
Scheme_Object *l = NULL;
Scheme_Object *a[2] = { NULL, NULL };
MZ_GC_DECL_REG(5);
MZ_GC_VAR_IN_REG(0, e);
MZ_GC_VAR_IN_REG(1, l);
MZ_GC_ARRAY_VAR_IN_REG(2, a, 2);
MZ_GC_REG();
declare_modules(e);
l = scheme_make_null();
l = scheme_make_pair(scheme_intern_symbol("run"), l);
l = scheme_make_pair(scheme_intern_symbol("quote"), l);
a[0] = l;
a[1] = scheme_false;
scheme_dynamic_require(2, a);
MZ_GC_UNREG();
return 0;
}
int main(int argc, char *argv[])
{
return scheme_main_setup(1, run, argc, argv);
}
"main.c"
#include "scheme.h"
#include "base.c"
static int run(Scheme_Env *e, int argc, char *argv[])
{
Scheme_Object *curout = NULL, *v = NULL, *a[2] = {NULL, NULL};
Scheme_Config *config = NULL;
int i;
Scheme_Thread *th = NULL;
mz_jmp_buf * volatile save = NULL, fresh;
MZ_GC_DECL_REG(9);
MZ_GC_VAR_IN_REG(0, e);
MZ_GC_VAR_IN_REG(1, curout);
MZ_GC_VAR_IN_REG(2, save);
MZ_GC_VAR_IN_REG(3, config);
MZ_GC_VAR_IN_REG(4, v);
MZ_GC_VAR_IN_REG(5, th);
MZ_GC_ARRAY_VAR_IN_REG(6, a, 2);
MZ_GC_REG();
declare_modules(e);
v = scheme_intern_symbol("racket/base");
scheme_namespace_require(v);
config = scheme_current_config();
curout = scheme_get_param(config, MZCONFIG_OUTPUT_PORT);
th = scheme_get_current_thread();
for (i = 1; i < argc; i++) {
save = th->error_buf;
th->error_buf = &fresh;
if (scheme_setjmp(*th->error_buf)) {
th->error_buf = save;
return -1; /* There was an error */
} else {
v = scheme_eval_string(argv[i], e);
scheme_display(v, curout);
v = scheme_make_char('\n');
scheme_display(v, curout);
/* read-eval-print loop, uses initial Scheme_Env: */
a[0] = scheme_intern_symbol("racket/base");
a[1] = scheme_intern_symbol("read-eval-print-loop");
v = scheme_dynamic_require(2, a);
scheme_apply(v, 0, NULL);
th->error_buf = save;
}
}
MZ_GC_UNREG();
return 0;
}
int main(int argc, char *argv[])
{
return scheme_main_setup(1, run, argc, argv);
}
Strictly speaking, the config and v variables above need not be registered with the garbage collector, since their values are not needed across function calls that allocate. The code is much easier to maintain, however, when all local variables are registered and when all temporary values are put into variables.
9.3 Flags and Hooks
The following flags and hooks are available when Racket is embedded:
scheme_exit —
This pointer can be set to a function that takes an integer argument and returns void; the function will be used as the default exit handler. The default is NULL. scheme_make_stdin, scheme_make_stdout, scheme_make_stderr, —
These pointers can be set to a function that takes no arguments and returns a Racket port Scheme_Object * to be used as the starting standard input, output, and/or error port. The defaults are NULL. Setting the initial error port is particularly important for seeing unexpected error messages if stderr output goes nowhere. scheme_console_output —
This pointer can be set to a function that takes a string and a intptr_t string length; the function will be called to display internal Racket warnings and messages that possibly contain non-terminating nuls. The default is NULL. scheme_check_for_break —
This points to a function of no arguments that returns an integer. It is used as the default user-break polling procedure in the main thread. A non-zero return value indicates a user break, and each time the function returns a non-zero value, it counts as a new break signal (though the break signal may be ignored if a previous signal is still pending). The default is NULL. scheme_case_sensitive —
If this flag is set to a non-zero value before scheme_basic_env is called, then Racket will not ignore capitalization for symbols and global variable names. The value of this flag should not change once it is set. The default is zero. scheme_allow_set_undefined —
This flag determines the initial value of compile-allow-set!-undefined. The default is zero. scheme_console_printf —
This function pointer was left for backward compatibility. The default builds a string and calls scheme_console_output.
|
|
|
|
The function calls scheme_seal_parameters automatically.
|
|
|