racket/pkgs/racket-doc/scribblings/inside/extensions.scrbl
Matthew Flatt 710320e3dc "Mac OS X" -> "Mac OS"
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.
2016-12-23 12:18:36 -07:00

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")]
@; ----------------------------------------------------------------------