2 Embedding into a Program (CS)
To embed Racket CS in a program, follow these steps:
Locate or build the Racket CS library.
On Unix, the library is "libracketcs.a". Building from source and installing places the libraries into the installation’s "lib" directory.
On Windows, link to "libracketcsx.dll" (where x represents the version number). At run time, either "libracketcsx.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" uses the latter strategy.) See also Linking to DLLs on Windows.
On Mac OS, besides "libracketcs.a" for static linking, a dynamic library is 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. 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). When targeting the Hardened Runtime, you must enable the “Allow Unsigned Executable Memory” entitlement, otherwise you will run into “out of memory” errors when calling racket_boot.
For each C file that uses Racket library functions, #include the files "chezscheme.h" and "racketcs.h".
The "chezscheme.h" and "racketcs.h" files are 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.
In your program, call racket_boot. The racket_boot function takes a pointer to a racket_boot_arguments_t for configuring the Racket instance. After zeroing out the racket_boot_arguments_t value (typicially with memset), only the following fields are required to be set:
exec_file —
a path to be reported by (find-system-path 'exec-file), usually argv[0] for the argv received by your program’s main. boot1_path —
a path to "petite.boot". Use a path that includes at least one directory separator. boot2_path —
a path to "scheme.boot" (with a separator). boot3_path —
a path to "racket.boot" (which a separator).
The "petite.boot", "scheme.boot", and "racket.boot" files are distributed with the Racket software in the installation’s "lib" directory for Windows, and they are distributed within the "Racket" framework on Mac OS X; they must be built from source on Unix. These files can be combined into a single file—
or even embedded into the executable— as long as the boot1_offset, boot2_offset, and boot3_offset fields of racket_boot_arguments_t are set to identify the starting offset of each boot image in the file. See Embedding Files in Executable Sections for advice on embedding files like "petite.boot" in an executable.
Configure the main thread’s 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 compiled form as encapsulated in a static array. The generated C file defines a declare_modules function that takes no arguments and 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, set fields like collects_dir, config_dir, and/or argv in the racket_boot_arguments_t passed to racket_boot to locate collections/packages and initialize the namespace the same way as when running the racket executable.
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 bytes-open-converter. Set dll_dir in racket_boot_arguments_t to register ‹dest-dir› so that those DLLs can be found at run time.
Instead of using --c-mods with raco ctool, you can use --mods, embed the file content (see Embedding Files in Executable Sections) and load the content with racket_embedded_load_file_region.
Access Racket through racket_dynamic_require, racket_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 the seal function provided by the primitive #%boot module afterward. The snapshot of parameter values taken by seal is used for certain privileged operations, such as installing a PLaneT package.
Compile the program and link it with the Racket libraries.
Racket values may be moved or garbage collected any time that racket_... functions are used to run Racket code. Do not retain a reference to any Racket value across such a call.
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). Copies of "petite.boot", "scheme.boot", and "racket.boot" must be in the current directory on startup.
"main.c"
#include <string.h>
#include "chezscheme.h"
#include "racketcs.h"
#include "run.c"
int main(int argc, char *argv[])
{
racket_boot_arguments_t ba;
memset(&ba, 0, sizeof(ba));
ba.boot1_path = "./petite.boot";
ba.boot2_path = "./scheme.boot";
ba.boot3_path = "./racket.boot";
ba.exec_file = argv[0];
racket_boot(&ba);
declare_modules();
ptr mod = Scons(Sstring_to_symbol("quote"),
Scons(Sstring_to_symbol("run"),
Snil));
racket_dynamic_require(mod, Sfalse);
return 0;
}
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 <string.h>
#include "chezscheme.h"
#include "racketcs.h"
#include "base.c"
static ptr to_bytevector(char *s);
int main(int argc, char *argv[])
{
racket_boot_arguments_t ba;
memset(&ba, 0, sizeof(ba));
ba.boot1_path = "./petite.boot";
ba.boot2_path = "./scheme.boot";
ba.boot3_path = "./racket.boot";
ba.exec_file = argv[0];
racket_boot(&ba);
declare_modules();
racket_namespace_require(Sstring_to_symbol("racket/base"));
{
int i;
for (i = 1; i < argc; i++) {
ptr e = to_bytevector(argv[i]);
e = Scons(Sstring_to_symbol("open-input-bytes"),
Scons(e, Snil));
e = Scons(Sstring_to_symbol("read"), Scons(e, Snil));
e = Scons(Sstring_to_symbol("eval"), Scons(e, Snil));
e = Scons(Sstring_to_symbol("println"), Scons(e, Snil));
racket_eval(e);
}
}
{
ptr rbase_sym = Sstring_to_symbol("racket/base");
ptr repl_sym = Sstring_to_symbol("read-eval-print-loop");
racket_apply(Scar(racket_dynamic_require(rbase_sym,
repl_sym)),
Snil);
}
return 0;
}
static ptr to_bytevector(char *s)
{
iptr len = strlen(s);
ptr bv = Smake_bytevector(len, 0);
memcpy(Sbytevector_data(bv), s, len);
return bv;
}
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.