
Although "macOS" is the correct name for Apple's current desktop OS, we've decided to go with "Mac OS" to cover all of Apple's Unix-like desktop OS versions. The label "Mac OS" is more readable, clear in context (i.e., unlikely to be confused with the Mac OSes that proceeded Mac OS X), and as likely to match Apple's future OS names as anything.
251 lines
9.7 KiB
Racket
251 lines
9.7 KiB
Racket
#lang scribble/doc
|
|
@(require "utils.rkt")
|
|
|
|
@title[#:tag "Writing Racket Extensions"]{Writing Racket Extensions}
|
|
|
|
@section-index["extending Racket"]
|
|
|
|
As noted in @secref["embedding-and-extending"], writing Racket code
|
|
and using the @seclink["top" #:doc '(lib
|
|
"scribblings/foreign/foreign.scrbl")]{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.
|
|
|
|
The process of creating an extension for Racket 3m or Racket CGC (see
|
|
@secref["CGC versus 3m"]) is essentially the same, but the process for
|
|
3m is most easily understood as a variant of the process for CGC.
|
|
|
|
@section{CGC Extensions}
|
|
|
|
|
|
To write a C/C++-based extension for Racket CGC, follow these
|
|
steps:
|
|
|
|
@itemize[
|
|
|
|
@item{@index['("header files")]{For} each C/C++ file that uses
|
|
Racket library functions, @cpp{#include} the file
|
|
@as-index{@filepath{escheme.h}}.
|
|
|
|
This file is distributed with the Racket software in an
|
|
@filepath{include} directory, but if @|mzc| is used to
|
|
compile, this path is found automatically.}
|
|
|
|
|
|
@item{Define the C function @cppi{scheme_initialize}, which takes a
|
|
@cpp{Scheme_Env*} namespace (see @secref["im:env"]) and returns a
|
|
@cpp{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 @racket[load-extension] the first time in a
|
|
given @|tech-place|; the return value from @cpp{scheme_initialize} is used
|
|
as the return value for @racket[load-extension]. The namespace
|
|
provided to @cpp{scheme_initialize} is the current namespace when
|
|
@racket[load-extension] is called.}
|
|
|
|
|
|
@item{Define the C function @cppi{scheme_reload}, which has the same
|
|
arguments and return type as @cpp{scheme_initialize}.
|
|
|
|
This function is called if @racket[load-extension] is called a second
|
|
time (or more times) for an extension in a given @|tech-place|. Like
|
|
@cpp{scheme_initialize}, the return value from this function is the
|
|
return value for @racket[load-extension].}
|
|
|
|
|
|
@item{Define the C function @cppi{scheme_module_name}, which takes
|
|
no arguments and returns a @cpp{Scheme_Object*} value, either a
|
|
symbol or @cpp{scheme_false}.
|
|
|
|
The function should return a symbol when the effect of calling
|
|
@cpp{scheme_initialize} and @cpp{scheme_reload} is only to declare
|
|
a module with the returned name. This function is called when the
|
|
extension is loaded to satisfy a @racket[require] declaration.
|
|
|
|
The @cpp{scheme_module_name} function may be called before
|
|
@cpp{scheme_initialize} and @cpp{scheme_reload}, after those
|
|
functions, or both before and after, depending on how the extension
|
|
is loaded and re-loaded.}
|
|
|
|
|
|
@item{Compile the extension C/C++ files to create platform-specific
|
|
object files.
|
|
|
|
The @as-index{@|mzc|} compiler, which is distributed with Racket,
|
|
compiles plain C files when the @as-index{@DFlag{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 @exec{gcc} in the path, or a Mac OS system with Apple's
|
|
developer tools installed, then using @|mzc| is typically easier than
|
|
working with the C compiler directly. Use the @as-index{@DFlag{cgc}}
|
|
flag to indicate that the build is for use with Racket CGC.}
|
|
|
|
|
|
@item{Link the extension C/C++ files with
|
|
@as-index{@filepath{mzdyn.o}} (Unix, Mac OS) or
|
|
@as-index{@filepath{mzdyn.obj}} (Windows) to create a shared object. The
|
|
resulting shared object should use the extension @filepath{.so} (Unix),
|
|
@filepath{.dll} (Windows), or @filepath{.dylib} (Mac OS).
|
|
|
|
The @filepath{mzdyn} object file is distributed in the installation's
|
|
@filepath{lib} directory. For Windows, the object file is in a
|
|
compiler-specific sub-directory of @filepath{racket\lib}.
|
|
|
|
The @|mzc| compiler links object files into an extension when the
|
|
@as-index{@DFlag{ld}} flag is specified, automatically locating
|
|
@filepath{mzdyn}. Again, use the @DFlag{cgc} flag with @|mzc|.}
|
|
|
|
@item{Load the shared object within Racket using
|
|
@racket[(load-extension _path)], where @racket[_path] is the name of
|
|
the extension file generated in the previous step.
|
|
|
|
Alternately, if the extension defines a module (i.e.,
|
|
@cpp{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 @racket[require] is used. The
|
|
special directory is a platform-specific path that can be obtained by
|
|
evaluating @racket[(build-path "compiled" "native"
|
|
(system-library-subpath))]; see @racket[load/use-compiled] for more
|
|
information. For example, if the shared object's name is
|
|
@filepath{example_rkt.dll}, then @racket[(require "example.rkt")] will
|
|
be redirected to @filepath{example_rkt.dll} if the latter is placed in
|
|
the sub-directory @racket[(build-path "compiled" "native"
|
|
(system-library-subpath))] and if @filepath{example.rkt} does not
|
|
exist or has an earlier timestamp.
|
|
|
|
Note that @racket[(load-extension _path)] within a @racket[module]
|
|
does @italic{not} introduce the extension's definitions into the
|
|
module, because @racket[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
|
|
@racket[require].}
|
|
|
|
]
|
|
|
|
@index['("allocation")]{@bold{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 @cppi{scheme_malloc}. However,
|
|
static variables that contain pointers to collectable memory must be
|
|
registered using @cppi{scheme_register_extension_global} (see
|
|
@secref["im:memoryalloc"]); even then, such static variables must be
|
|
thread-local (in the OS-thread sense) to work with multiple
|
|
@|tech-place|s (see @secref["places"]).
|
|
|
|
As an example, the following C code defines an extension that returns
|
|
@racket["hello world"] when it is loaded:
|
|
|
|
@verbatim[#:indent 2]{
|
|
#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 @filepath{hw.c}, the extension
|
|
is compiled on Unix with the following two commands:
|
|
|
|
@commandline{raco ctool --cgc --cc hw.c}
|
|
@commandline{raco ctool --cgc --ld hw.so hw.o}
|
|
|
|
(Note that the @DFlag{cgc}, @DFlag{cc}, and @DFlag{ld} flags are each
|
|
prefixed by two dashes, not one.)
|
|
|
|
The @filepath{collects/mzscheme/examples} directory in the Racket
|
|
distribution contains additional examples.
|
|
|
|
@section{3m Extensions}
|
|
|
|
To build an extension to work with Racket 3m, the CGC instructions
|
|
must be extended as follows:
|
|
|
|
@itemize[
|
|
|
|
@item{Adjust code to cooperate with the garbage collector as
|
|
described in @secref["im:3m"]. Using @|mzc| with the
|
|
@as-index{@DFlag{xform}} might convert your code to implement part of
|
|
the conversion, as described in @secref["im:3m:mzc"].}
|
|
|
|
@item{In either your source in the in compiler command line,
|
|
@cpp{#define} @cpp{MZ_PRECISE_GC} before including
|
|
@filepath{escheme.h}. When using @|mzc| with the @DFlag{cc} and
|
|
@as-index{@DFlag{3m}} flags, @cpp{MZ_PRECISE_GC} is automatically
|
|
defined.}
|
|
|
|
@item{Link with @as-index{@filepath{mzdyn3m.o}} (Unix, Mac OS) or
|
|
@as-index{@filepath{mzdyn3m.obj}} (Windows) to create a shared
|
|
object. When using @|mzc|, use the @DFlag{ld} and @DFlag{3m} flags
|
|
to link to these libraries.}
|
|
|
|
]
|
|
|
|
For a relatively simple extension @filepath{hw.c}, the extension is
|
|
compiled on Unix for 3m with the following three commands:
|
|
|
|
@commandline{raco ctool --xform hw.c}
|
|
@commandline{raco ctool --3m --cc hw.3m.c}
|
|
@commandline{raco ctool --3m --ld hw.so hw_3m.o}
|
|
|
|
Some examples in @filepath{collects/mzscheme/examples} work with
|
|
Racket 3m in this way. A few examples are manually instrumented, in
|
|
which case the @DFlag{xform} step should be skipped.
|
|
|
|
@section{Declaring a Module in an Extension}
|
|
|
|
To create an extension that behaves as a module, return a symbol from
|
|
@cpp{scheme_module_name}, and have @cpp{scheme_initialize} and
|
|
@cpp{scheme_reload} declare a module using @cpp{scheme_primitive_module}.
|
|
|
|
For example, the following extension implements a module named
|
|
@racket[hello] that exports a binding @racket[greeting]:
|
|
|
|
@verbatim[#:indent 2]{
|
|
#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 @exec{mzc} commands:
|
|
|
|
@commandline{raco ctool --xform hi.c}
|
|
@commandline{raco ctool --3m --cc hi.3m.c}
|
|
@commandline{mkdir -p compiled/native/i386-linux/3m}
|
|
@commandline{raco ctool --3m --ld compiled/native/i386-linux/3m/hi_rkt.so hi_3m.o}
|
|
|
|
The resulting module can be loaded with
|
|
|
|
@racketblock[(require "hi.rkt")]
|
|
|
|
@; ----------------------------------------------------------------------
|
|
|