12.  Advanced Topics  

12.1.  Converting between Symbols, Integers, and Bitmasks  

      Symbols are frequently used as the arguments to Scheme primitives which call an underlying C or C++ function with some kind of bitmask or with a predefined enumeration constant or preprocessor symbol. For example, the primitive dbm-open shown in Figure @(ndbm2) above uses symbols to represent the symbolic constants passed to dbm_open(). Similarly, a Scheme primitive corresponding to the UNIX system call open() could receive a list of symbols represending the logical OR of the usual open() flags, so that one can write Scheme code such as:

(let ((tty-fd (unix-open "/dev/ttya"    '(read write exclusive)))
      (tmp-fd (unix-open "/tmp/somefile '(write create))))
	...

      To facilitate conversion of symbols to C integers or enumeration constants and vice versa, these two functions are provided:

unsigned long Symbols_To_Bits(Object syms, int mask_flag,
    SYMDESCR *table);
Object Bits_To_Symbols(unsigned long bits, int mask_flag,
    SYMDESCR *table);
The type SYMDESCR is defined as:
typedef struct {
	char *name;
	unsigned long val;
} SYMDESCR;

      Symbols_To_Bits() converts a symbol or a list of symbols to an integer; Bits_To_Symbols() is the reverse operation and is usually applied to the return value of a C/C++ function to convert it to a Scheme representation. Both functions receive as the third argument a table specifying the correspondence between symbols and C constants; each table entry is a pair consisting of the name of a symbol as a C string and an integer val (typically an enumeration constant or a #define constant). Each SYMDESCR array is terminated by an entry with a zero name component:

SYMDESCR lseek_syms[] = {
	{ "set",      SEEK_SET },
	{ "current",  SEEK_CUR },
	{ "end",      SEEK_END },
	{ 0, 0 }
};

      The second argument to the conversion functions controls whether a single symbol is converted to an integer or vice versa (mask_flag is zero), or whether a list of symbols is converted to the logical OR of a set of matching values or vice versa (mask_flag is non-zero). Symbols_To_Bits() signals an error if the symbol does not match any of the names in the given table or, if mask_flag is non-zero, if any of the list elements does not match. The empty list is converted to zero. If Bits_To_Symbols() is called with a non-zero mask_flag, it matches the val components against the bits argument using logical AND. Regardless of mask_flag, Bits_To_Symbols returns the empty list if no match occurs. Figure @(ndbm3) shows an improved version of p_dbm_open() using Symbols_To_Bits() in place of nested if-statements.


static SYMDESCR flag_syms[] = {
	{ "reader", O_RDONLY },
	{ "writer", O_RDWR },
	{ "create", O_RDWR|O_CREAT },
	{ 0, 0 }
};

Object p_dbm_open(int argc, Object *argv) {
	DBM *dp;
	Object d;

	dp = dbm_open(Get_String(argv[0]),
	    Symbols_To_Bits(argv[1], 0, flag_syms),
	    argc == 3 ? Get_Integer(argv[2]) : 0666);
	if (dp == 0)
		return False;
	d = Alloc_Object(sizeof(struct s_dbm), t_dbm, 0);
	DBMF(d)->dbm = dp;
	DBMF(d)->alive = 1;
	return d;
}
Figure 7: Improved version of dbm-open using Symbols_To_Bits()


      A Scheme primitive calling the UNIX system call access() could use Symbols_To_Bits() with a non-zero mask_flag to construct a bitmask:

Object p_access(Object fn, Object mode) {
	access(Get_String(fn), (int)Symbols_To_Bits(mode, 1, access_syms));
	...
where access_syms is defined as:
static SYMDESCR access_syms[] = {
	{ "read",       R_OK },
	{ "write",      W_OK },
	{ "execute",    X_OK },
	{ 0, 0 }
};
Note that in this example the empty list can be passed as the mode argument to test for existence of the file, because in this case Symbols_To_Bits() returns zero (the value of F_OK).

12.2.  Calling Scheme Procedures, Evaluating Scheme Code  

      A Scheme procedure can be called from within C or C++ code using the function

Object Funcall(Object fun, Object argl, int eval_flag);
The first argument is the Scheme procedure--either a primitive procedure (T_Primitive) or a compound procedure (T_Compound). The second argument is the list of arguments to be passed to the procedure, as a Scheme list. The third argument, if non-zero, specifies that the arguments need to be evaluated before calling the Scheme procedure. This is usually not the case (except in some special forms). The return value of Funcall() is the result of the Scheme procedure.

      Funcall() is frequently used from within C callback functions that can be registered for certain events, such as the user-supplied X11 error handlers, X11 event handlers, timeout handlers, the C++ new handler, etc. Here, use of Funcall() allows to register a user-defined Scheme procedure for this event from within a Scheme program. As an example, Figure @(funcall) shows the generic signal handler that is associated with various UNIX signals by the UNIX extension.


void scheme_signal_handler(int sig) {
	Object fun, args;

	Set_Error_Tag("signal-handler");
	Reset_IO(1);
	args = Bits_To_Symbols((unsigned long)sig, 0, signal_syms);
	args = Cons(args, Null);
	fun = VECTOR(handlers)->data[sig];
	if (TYPE(fun) != T_Compound)
		Fatal_Error("no handler for signal %d", sig);
	(void)Funcall(fun, args, 0);
	Printf(Curr_Output_Port, "\n\7Signal!\n");
	(void)P_Reset();
	/*NOTREACHED*/
}
Figure 8: Using Funcall() to call a Scheme procedure


      The signal handler shown in Figure @(funcall) uses the signal number supplied by the system to index a vector of user-defined Scheme procedures (that is, Objects of type T_Compound). Reset_IO() is used here to ensure that the current input and output port are in defined state when the Scheme signal handler starts executing. The argument list is constructed by calling Cons(); it consists of a single element--the signal number as a Scheme symbol. signal_syms is an array of SYMDESCR records that maps the UNIX signal names (sighup, sigint, etc.) to corresponding Scheme symbols of the same names. The Scheme procedure called from the signal handler is not supposed to return (it usually invokes a continuation); therefore the result of Funcall() is ignored. In case the Scheme handler (and thus the call to Funcall()) does return, a message is printed and the primitive reset is called to return to the application's toplevel or standard Scheme toplevel.

      An S-expression can be evaluated by calling the function

Object Eval(Object expr);
which is identical to the primitive eval (P_Eval() in C), except that no optional environment can be supplied. Eval() is very rarely used by extensions or applications, mainly by implementations of new special forms. Both Eval() and Funcall() can trigger a garbage collection; all local variables holding Scheme Objects with heap pointers must be properly registered with the garbage collector to survive calls to these functions.

      Occasionally an S-expression needs to be evaluated that exists as a C string, for example, when a Scheme expression has been entered through a ``text widget'' in a graphical user interface. Here, evaluation requires calling the Scheme reader to parse the expression; therefore a straightforward solution is to create a string port holding the string and then just ``load'' the contents of the port:

void eval_string(char *expr) {
	Object port; GC_Node;

	port = P_Open_Input_String(Make_String(expr, strlen(expr)));
	GC_Link(port);
	Load_Source_Port(port);
	GC_Unlink;
	(void)P_Close_Input_Port(port);
}
If a more sophisticated function is required, the eval-string extension included in the Elk distribution can be used (``lib/misc/elk-eval.c''). This extension provides a function
char *Elk_Eval(char *expr);
that converts the result of evaluating the stringized expression back to a C string and returns it as a result. A null pointer is returned if an error occurs during evaluation.

      Applications should not use this function as the primary interface to the extension language. In contrast to languages such as Tcl, the semantic concepts and data structures of Scheme are not centered around strings, and strings are not a practicable representation for S-expressions. Instead, applications should pass control to the extension language by calling Scheme procedures (using Funcall()) or by loading files containing Scheme code. The extension language then calls back into the application's C/C++ layer by invoking application-supplied Scheme primitives and other forms of callbacks as explained in section @(ch-control).

12.3.  GC-Protecting Global Objects  

      Section @(ch-gc) explained when--and how--to register with the garbage collector function-local Object variables holding heap pointers. Similarly, global variables must usually be added to the set of reachable objects as well if they are to survive garbage collections (a useful exception to this rule will be introduced in section @(ch-term)). In contrast to local variables, global variables are only made known to the garbage collector once--after initialization--as their lifetime is that of the entire program. To add a global variable to the garbage collector's root set, the macro

Global_GC_Link(obj)
must be called with the properly initialized variable of type Object. The macro takes the address of the specified object. If that is a problem, an equivalent functional interface can be used:
void Func_Global_GC_Link(Object *obj_ptr);
This function must be supplied the address of the global variable to be registered with the garbage collector.

      When writing extensions that maintain global Object variables, Global_GC_Link() (or Func_Global_GC_Link()) is usually called from within the extension initialization function right after each variable is assigned a value. For instance, the global Scheme vector handlers that was used in Figure @(funcall) to associate procedures with UNIX signals is initialized and GC-protected as follows:

void elk_init_unix_signal(void) {
	handlers = Make_Vector(NSIG, False);
	Global_GC_Link(handlers);
	...
}
NSIG is the number of UNIX signal types as defined by the system include file. The signal handling Scheme procedures that are inserted into the vector later need not be registered with the garbage collector, because they are now reachable through another object which itself is reachable.

12.3.1.  Dynamic C Data Structures  

      Dynamic data structures, such as the nodes of a linked list containing Scheme Objects, cannot be easily registered with the garbage collector. The simplest solution is to build these data structures in Scheme rather than in C or C++ in the first place. For example, a linked list of Scheme objects can be built from Scheme pairs much more naturally and more straightforward than from C structures or the like, in particular if the list will be traversed and manipulated using Scheme primitives anyway. Besides, data structures programmed in Scheme benefit from automatic memory management, whereas use of malloc() and free() in C frequently is a source of memory leaks and related errors.

      If for some reason a dynamic data structure must be built in C or C++ rather than in Scheme, reachability problems can be avoided by inserting all Objects into a global, GC-protected vector (such as handlers in Figure @(funcall)) and then use the corresponding vector indexes rather than the actual Objects. This sounds more difficult than it really is; Appendix B shows the complete source code of a small module to register Objects in a Scheme vector. The module exports three functions: register_object() inserts an Object into the vector and returns the index as an int; deregister_object() removes an Object with a given index from the vector; and get_object() returns the Object stored under a given index. register_object() dynamically grows the vector to avoid artificial limits.

      A dynamic data structure (e.g. linked list) implementation using this module would call register_object() when inserting a new Object into the list and then use the integer return value in place of the Object itself. Similarly, it would call deregister_object() whenever a node is removed from the list. get_object() would be used to retrieve the Object associated with a given list element. Note that with these functions the same Object can be registered multiple times (each time under a new index) without having to maintain reference counts: the garbage collector does not care how often a particular Object is traversed during garbage collection, as long as it will be reached at least once.

12.4.  Weak Pointers and Object Termination  

      A data structure implementation may deliberately use Objects that are not added to the global set of reachable pointers (as described in the previous section) and are thus invisible to the garbage collector. In this case, it becomes possible to determine whether or not garbage collection has found any other pointers to the same Scheme objects. This property can be exploited in several ways by extensions or applications using Elk.

      Pointers that are not included in the garbage collector's reachability search are called ``weak pointers''. The memory occupied by a Scheme object that is only referenced by weak pointers will be reclaimed. The term weak expresses the notion that the pointer is not strong enough to prevent the object it points to from being garbage collected. Code using weak pointers can scan the pointers immediately after each garbage collection and check whether the target object has been visited by the just-finished garbage collection. If this is the case, normal (strong) pointers to the object must exist (which can therefore be considered ``live''), and the weak pointer is updated manually to point to the object's new location. On the other hand, if the object has not been visited, no more (normal) references to it exist and the memory occupied by it has been reclaimed.

      Weak pointers are useful in implementing certain types of data structures where the sole existence of a (weak) pointer to an object from within this data structure should not keep the object alive (weak sets, populations, certain kinds of hash tables, etc.). Objects that are not reachable through strong pointers are then removed from the weak data structure after garbage collection. In this case, it is frequently useful to invoke a ``termination function'' for each such object, e.g. for objects that contain resources of which only a finite amount is available, such as UNIX file descriptors (or FILE structures), X displays and windows, etc. The termination function for Scheme ports closes the file pointer encapsulated in a port object if it is still open; likewise, the termination function for X windows closes the window and thereby removes it from the display, and so on. Thus, should an object holding some kind of resource go inaccessible before it was terminated ``properly'' by calling the respective Scheme primitive (close-input-port, close-output-port, destroy-window, etc.), then resource will be reclaimed after the next garbage collection run.

12.4.1.  Using Weak Pointers  

      Code using weak pointers must scan the pointers immediately after each garbage collection, but before the interpreter resumes normal operation, because the memory referenced by the weak pointers can be reused the next time heap space is requested. This can be accomplished by registering a so-called ``after-GC function. Elk's garbage collector invokes all after-GC functions (without arguments) upon completion. To register an after-GC functions, the function

void Register_After_GC((void (*func)(void)));
is used, typically in an extension initializer. Similarly, extensions and applications can register ``before-GC functions'' using
void Register_Before_GC((void (*func)(void)));
These functions are called immediately before each garbage collection and may be used, for instance, to change the application's cursor to an hourglass symbol. After-GC and before-GC functions must not trigger another garbage collection.

      An after-GC function scanning a set of weak pointers makes use of the three macros IS_ALIVE(), WAS_FORWARDED(), and UPDATE_OBJ(). For example, an after-GC function scanning a table of elements holding Objects with weak pointers could be written as shown in Figure @(aftergc).


void scan_weak_table(void) {
	int i;

	for (i = 0; i < table_size; i++) {
		Object obj = table[i].obj;
		if (IS_ALIVE(obj)) {            /* object is still reachable */
			if (WAS_FORWARDED(obj))
				UPDATE_OBJ(obj);
		} else {
			terminate_object(obj);  /* object is dead; finalize... */
			table[i] = 0;           /* and remove it from the table */
		}
	}
}
Figure 9: After-GC function that scans a table containing weak pointers


      The function scan_weak_table() shown in Figure @(aftergc) can then be registered as an after-GC function by invoking

Register_After_GC(scan_weak_table);

      The then-part of the if-statement in scan_weak_table() is entered if the just-completed garbage collection has encountered any pointers to the Scheme object pointed to by obj; in this case the pointer conveyed in obj is updated manually using UPDATE_OBJ() (when using the generational garbage collector included in Elk, reachability of an object does not necessarily imply that it was forwarded, hence the additional call to WAS_FORWARDED()). If IS_ALIVE() returns false, no more strong pointers to the object exist and it can be terminated and removed from the weak data structure. terminate_object() typically would release any external resources contained in the Scheme object, but it must neither create any new objects nor attempt to ``revive'' the dead object in any way (e.g. create a new strong pointer to it by inserting it into another, live object).

12.4.2.  Functions for Automatic Object Termination  

      As automatic termination of Scheme objects using user-supplied termination functions is the most frequent use of weak pointers, Elk offers a set of convenience functions for this purpose. Extensions and applications can insert Objects into a weak list maintained by Elk and remove them from the list using the two functions

void Register_Object(Object obj, char *group,
                     (Object (*term)(Object)), int leader_flag);
void Deregister_Object(Object obj);

      term is the termination function that is called automatically with obj when the object becomes unreachable (its result is not used); group is an opaque ``cookie'' associated with obj and can be used to explicitly terminate all objects with the same value for group; a non-zero leader_flag indicates that obj is the ``leader'' of the specified group. Elk automatically registers an after-GC function to scan the weak list maintained by these two functions and to call the term function for all objects that could be proven unreachable by the garbage collector, similar to the function shown in Figure @(aftergc).

      Object termination takes place in two phases: first all objects registered with a zero leader_flag are terminated, after that the termination functions of the leaders are invoked. This group and leader notion is used, for example, by the Xlib extension to associate windows (and other resources) with an X display: the ID of the display to which a window belongs is used as the window's group, and the display is marked as the group leader. Thus, if a display becomes unreachable or is closed by the program, all its windows are closed before the display is finally destroyed[note 5] .

Two additional functions are provided for explicitly calling the termination functions:

void Terminate_Type(int type);
void Terminate_Group(char *group);
Terminate_Type() invokes the termination function (if any) for all objects of a given type and deletes them from the weak list. For example, to close all ports currently held open by Elk (and thus apply fclose() to the FILE pointers embedded in them), one would call
Terminate_Type(T_Port)
Terminate_Group() calls the termination functions of all non-leader objects belonging to the specified group.

Finally, another function, Find_Object(), locates an object in the weak list:

Object Find_Object(int type, char *group,
		   (int (*match_func)(Object, ...)), ...);
Arguments are a Scheme type, a group, and a match function called once for each object in the weak list that has the specified type and group. The match function is passed the Object and the remaining arguments to Find_Object(), if any. If the match function returns true for an object, this object becomes the return value of Find_Object(); otherwise it returns Null.

      Complicated as it may seem, Find_Object() is quite useful--extensions can check whether a Scheme object with certain properties has already been registered with the weak list earlier and, if this is the case, return this object instead of creating a new one. This is critical for Scheme objects encapsulating some kind of external resource, such as file descriptors or X windows. Consider, for example, a Scheme primitive that obtains the topmost window on a given X display and returns it as a Scheme window object. If the primitive just were to instantiate a Scheme object encapsulating the corresponding X window ID for each call, it would become possible for two or more distinct Scheme window objects to reference the same real X window. This is not acceptable, because two Scheme objects pointing to the same X object should certainly be equal in the sense of eq?, not to mention the problems that would ensue if one of the Scheme window objects were closed (thereby destroying the underlying X window) and the second one were still be operated on afterwards. Example uses of Find_Object() can be found in the Xlib extension and in the Xt extension that are included in the Elk distribution.

12.5.  Errors  

      User-supplied code can signal an error by calling Primitive_Error() with a format string and as many additional arguments (Objects) as there are format specifiers in the format string:

void Primitive_Error(char *fmt, ...);
Primitive_Error() calls the default or user-defined error handler as described in the Elk Reference Manual, passing it an ``error tag'' identifying the source of the error, the format string, and the remaining arguments. A special format specifier ``~E'' can be used to interpolate the standard error message text corresponding to the UNIX error number errno; this is useful for primitives that invoke UNIX system calls or certain C library functions (if ``~e'' is used, the first character of the text is converted to lower case). If this format specifier is used, the current errno must be assigned to a variable Saved_Errno prior to calling Primitive_Error() to prevent it from being overwritten by the next system call or C library function. Primitive_Error() does not return.

      Applications that need to supply their own error handler by redefining error-handler usually do so in Scheme, typically at the beginning of the initial Scheme file loaded in main().

      If Primitive_Error() is called from within a C function that implements a Scheme primitive, an error tag is supplied by Elk (the name of the primitive). Applications may set the error tag explicitly at the beginning of sections of C/C++ code that reside outside of primitives, for example, before loading an initial Scheme file in the application's main(). Two functions are provided to set and query the current error tag:

void Set_Error_Tag(const char *tag);
char *Get_Error_Tag(void);
The following three functions can be used by primitives to signal errors with standardized messages in certain situations:
void Range_Error(Object offending_obj);
void Wrong_Type(Object offending_obj, int expected_type);
void Wrong_Type_Combination(Object offending_obj, char *expected_type);
Range_Error() can be used when an argument to a primitive is out of range (typically some kind of index). Wrong_Type() signals a failed type-check for the given Object; the second argument is the expected type of the Object. This function is used, for example, by Check_Type(). Wrong_Type_Combination() is similar to Wrong_Type(); the expected type is specified as a string. This is useful if an Object can be a member of one out of two or more types, e.g. a string or a symbol.

Fatal errors can be signaled using the functions

void Fatal_Error(char *fmt, ...);
void Panic(char *msg);
Fatal_Error() passes its arguments to printf() and then terminates the program. Panic() is used in situations that ``cannot happen'' (failed consistency checks or failed assertions); it prints the specified message and terminates the program with a core dump.

12.6.  Exceptions  

      As explained in the Elk Reference Manual, a user-supplied Scheme procedure is called each time an exception is raised. Currently, the set of UNIX signals that are caught by the interpreter or an extension (at least interrupt and alarm) are used as exceptions. As signals occur asynchronously, extensions and applications must be able to protect non-reentrant or otherwise critical code sections from the delivery of signals. In particular, calls to external library functions are frequently not reentrant[note 6] and need to be protected from being disrupted.

      Extensions may call the macros Disable_Interrupts and Enable_Interrupts (without arguments) to enclose code fragments that must be protected from exceptions. Calls to these macros can be nested, and they are also available as Scheme primitives on the Scheme-language level. As all modern UNIX versions provide a facility to temporarily block the delivery of signals, a signal that occurs after a call to Disable_Interrupts will be delayed until the outermost matching Enable_Interrupts is executed. Two additional macros, Force_Disable_Interrupts and Force_Enable_Interrupts can be used to enable and disable signal delivery regarless of the current nesting level. Extensions that use additional signals (such as the alarm signal) must register these with the interpreter core to make sure they are included in the mask of signals that is maintained by Disable_Interrupts and Enable_Interrupts (the interface for registering signals is still being revised; refer to the source code of the UNIX extension for an example).

      The ability to protect code from exceptions is particularly useful for primitives that temporarily open a file or allocate some other kind of resource that must subsequently be released again. If the relevant code fragment were not enclosed by calls to Disable_Interrupts and Enable_Interrupts, an exception handler could abandon execution of the code section by calling a continuation, thus causing the file to remain open forever. While situations like this can be handled by dynamic-wind on the Scheme level, some form of try/catch facility is not available on the C-language level, and using the C function implementing the dynamic-wind primitive would be cumbersome.

The function

void Signal_Exit(int signal_number);
may be used as the handler for signals that must terminate the application; it ensures that the temporary files maintained by Elk are removed and calls the extension finalization functions in the normal way.

12.7.  Defining Scheme Variables  

      User-supplied C/C++ code can define global Scheme variables that are maintained as corresponding Object C variables. The Scheme interpreter itself defines several such variables, for example, the variable load-path (see section @(ch-dynl)) which can be modified and read both from Scheme and from C. The function Define_Variable() is used to define a Scheme variable and bind an initial value to it:

void Define_Variable(Object *var, const char *name, Object init);
var is the address of the C variable corresponding to the newly-created Scheme variable, name is the name of the Scheme variable, and init is its initial value. Define_Variable() calls Intern() to create the variable name included in the new binding and Func_Global_GC_Link() to properly register the C variable with the garbage collector.

The C side of a Scheme variable cannot be accessed directly; the functions

Var_Set(Object variable, Object value);
Var_Get(Object variable)
Var_Is_True(Object variable)
must be used instead to assign a value to the variable and to read its current value; the first argument to each function is the Object whose address was passed to Define_Variable(). Var_Is_True() is convenient for boolean variables and tests whether the contents of the variable is true in the sense of Truep(). As an example, Figure @(defvar) shows how the Xt extension defines a Scheme variable that is associated with the user-defined ``warning handler'' called by the Xt library to output warning messages.


Object V_Xt_Warning_Handler;

void Xt_Warning(char *msg) {
	Object args, fun;

	args = Cons(Make_String(msg, strlen(msg)), Null);
	fun = Var_Get(V_Xt_Warning_Handler);
	if (TYPE(fun) == T_Compound)
		(void)Funcall(fun, args, 0);
	else
		Printf(Curr_Output_Port, "%s\n", msg);
}

void elk_init_xt_error(void) {
	Define_Variable(&V_Xt_Warning_Handler, "xt-warning-handler", Null);
	XtSetWarningHandler(Xt_Warning);
}
Figure 10: The Xt extension defines a Scheme variable holding a ``warning handler''


      In the example in Figure @(defvar), the function Xt_Warning() is registered as the Xt ``warning handler'' by passing it to XtSetWarningHandler(). It is invoked by Xt with a warning message. The message is converted to a Scheme string, and, if the Scheme variable xt-warning-handler has been assigned a procedure, this procedure is called with the string using Funcall(). Otherwise the string is just sent to the current output port. The call to Define_Variable() in the extension initialization function associates the Scheme variable xt-warning-handler with the C variable V_Xt_Warning_Handler (as a convention, Elk uses the prefix ``V_'' for variables of this kind).

12.8.  Defining Readers  

      In addition or as an alternative to the constructor primitive for a new Scheme type, applications and extensions may define a reader function for each new type. The bitstring extension, for example, defines a reader to allow input of bitstring literals using the #*10110001 syntax. Each user-defined read syntax is introduced by the `#' symbol followed by one more character, identifying the type of the object. To define a reader, the following function is called (typically from within an extension initialization function):

void Define_Reader(int c,
    (Object (*func)(Object port, int c, int const_flag)));

      The arguments to Define_Reader() are the as yet unused character identifying the type (e.g. `*' for bitstrings) and a pointer to a reader function that is invoked by the Scheme parser whenever the newly defined syntax is encountered. This reader function is passed a Scheme input port from which it reads the next token, the character following the `#' symbol (to facilitate using the same reader for different types), and a flag indicating whether the newly-created object is expected to be made read-only (this is true when expressions are loaded from a file). The reader function must return a new object of the given type.

      You may want to refer to the bitstring extension included in the Elk distribution for an example definition of a reader function (``lib/misc/bitstring.c''), and for the macros that can be used by reader functions to efficiently read characters from a port.

12.9.  Fork Handlers  

      Extensions may need to be notified when a copy of the running interpreter (or application) is created by means of the fork() UNIX system call. For example, consider an extension that stores information in a temporary file and removes this file on termination of the program. If another extension created a copy of the running interpreter by calling fork(), the child process would remove the temporary file on exit--the file would not be available to the original instance of the interpreter (i.e. the parent process) any longer. To prevent premature removal of the file, the extension that owns it can define a fork handler by calling Register_Onfork() with a pointer to a C function:

void Register_Onfork((void (*func)(void)));
The function could create an additional link to the file, so that a child process would just remove this link on exit, leaving the original link intact.

      Extensions that use fork() without executing a new program in the child process (e.g. the UNIX extension which defines a unix-fork primitive) are required to call the function Call_Onfork() in the newly created child process to invoke all currently defined fork handlers:

void Call_Onfork(void);


Markup created by unroff 1.0,    September 24, 1996,    net@informatik.uni-bremen.de