10 Writing Racket Extensions
As noted in Embedding and Extending Racket , writing Racket code and using the foreign-function interface is usually a better option than writing an extension to Racket, but Racket also supports C-implemented extensions that plug more directly into the run-time system. (Racket CS does not have a similar extension interface.)
The process of creating an extension for Racket 3m or Racket CGC (see CGC versus 3m) is essentially the same, but the process for 3m is most easily understood as a variant of the process for CGC.
10.1 CGC Extensions
To write a C/C++-based extension for Racket CGC, follow these steps:
For each C/C++ file that uses Racket library functions, #include the file "escheme.h".
This file is distributed with the Racket software in an "include" directory, but if raco ctool is used to compile, this path is found automatically.
Define the C function scheme_initialize, which takes a Scheme_Env* namespace (see Namespaces and Modules) and returns a Scheme_Object* Racket value.
This initialization function can install new global primitive procedures or other values into the namespace, or it can simply return a Racket value. The initialization function is called when the extension is loaded with load-extension the first time in a given place; 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 in a given place. 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 raco ctool compiler, which is distributed with Racket, compiles plain C files when the --cc flag is specified. More precisely, raco ctool 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 Apple’s developer tools installed, then using raco ctool is typically easier than working with the C compiler directly. Use the --cgc flag to indicate that the build is for use with Racket CGC.
Link the extension C/C++ files with "mzdyn.o" (Unix, Mac OS) or "mzdyn.obj" (Windows) to create a shared object. The resulting shared object should use the extension ".so" (Unix), ".dll" (Windows), or ".dylib" (Mac OS).
The "mzdyn" object file is distributed in the installation’s "lib" directory. For Windows, the object file is in a compiler-specific sub-directory of "racket\lib".
The raco ctool compiler links object files into an extension when the --ld flag is specified, automatically locating "mzdyn". Again, use the --cgc flag with raco ctool.
Load the shared object within Racket using (load-extension path), where path is the name of the extension file generated in the previous step.
Alternately, if the extension defines a module (i.e., scheme_module_name returns a symbol), then place the shared object in a special directory with a special name, so that it is detected by the module loader when require is used. The special directory is a platform-specific path that can be obtained by evaluating (build-path "compiled" "native" (system-library-subpath)); see load/use-compiled for more information. For example, if the shared object’s name is "example_rkt.dll", then (require "example.rkt") will be redirected to "example_rkt.dll" if the latter is placed in the sub-directory (build-path "compiled" "native" (system-library-subpath)) and if "example.rkt" does not exist or has an earlier timestamp.
Note that (load-extension path) within a module does not introduce the extension’s definitions into the module, because load-extension is a run-time operation. To introduce an extension’s bindings into a module, make sure that the extension defines a module, put the extension in the platform-specific location as described above, and use require.
IMPORTANT: 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. However, static variables that contain pointers to collectable memory must be registered using scheme_register_extension_global (see Memory Allocation); even then, such static variables must be thread-local (in the OS-thread sense) to work with multiple places (see Racket BC and Places).
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_utf8_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 on Unix with the following two commands:
(Note that the --cgc, --cc, and --ld flags are each prefixed by two dashes, not one.)
The "collects/mzscheme/examples" directory in the Racket distribution contains additional examples.
10.2 3m Extensions
To build an extension to work with Racket 3m, the CGC instructions must be extended as follows:
Adjust code to cooperate with the garbage collector as described in Cooperating with 3m. Using raco ctool with the --xform might convert your code to implement part of the conversion, as described in Local Pointers and raco ctool --xform.
In either your source in the in compiler command line, #define MZ_PRECISE_GC before including "escheme.h". When using raco ctool with the --cc and --3m flags, MZ_PRECISE_GC is automatically defined.
Link with "mzdyn3m.o" (Unix, Mac OS) or "mzdyn3m.obj" (Windows) to create a shared object. When using raco ctool, use the --ld and --3m flags to link to these libraries.
For a relatively simple extension "hw.c", the extension is compiled on Unix for 3m with the following three commands:
Some examples in "collects/mzscheme/examples" work with Racket 3m in this way. A few examples are manually instrumented, in which case the --xform step should be skipped.
10.3 Declaring a Module in an Extension
To create an extension that behaves as a module, return a symbol from scheme_module_name, and have scheme_initialize and scheme_reload declare a module using scheme_primitive_module.
For example, the following extension implements a module named hi that exports a binding greeting:
#include "escheme.h" |
|
Scheme_Object *scheme_initialize(Scheme_Env *env) { |
Scheme_Env *mod_env; |
mod_env = scheme_primitive_module(scheme_intern_symbol("hi"), |
env); |
scheme_add_global("greeting", |
scheme_make_utf8_string("hello"), |
mod_env); |
scheme_finish_primitive_module(mod_env); |
return scheme_void; |
} |
|
Scheme_Object *scheme_reload(Scheme_Env *env) { |
return scheme_initialize(env); /* Nothing special for reload */ |
} |
|
Scheme_Object *scheme_module_name() { |
return scheme_intern_symbol("hi"); |
} |
This extension could be compiled for 3m on i386 Linux, for example, using the following sequence of mzc commands:
The resulting module can be loaded with
(require "hi.rkt")