6.  Static Linking  

      Linking user-supplied code with Elk statically can be used as an alternative to dynamic loading on platforms that do not support it, for applications with their own main(), and to avoid the overhead of loading frequently used Elk extensions. Dynamic loading and static linking may be used in combination-- additional object files can be loaded in a running executable formed by linking the Scheme interpreter with extensions or with an application (or parts thereof).

      When making the Scheme interpreter component of Elk, these executables and object files get installed (relative to your install_dir which usually is ``/usr/elk'' or ``/usr/local/elk''):

bin/scheme
The freestanding, plain Scheme interpreter.
lib/standalone.o
The Scheme interpreter as a relocatable object file which can be linked with user-supplied object files to form an executable. This object file contains a main() function; thus the Scheme interpreter starts up in the normal way when the executable is invoked.
lib/module.o
Like standalone.o, except that the object file does not export its own main() function. Therefore, the object files linked with it have to supply a main().

      The object file standalone.o is typically linked with a number of Elk extensions (e.g. the X11 extensions), while module.o is used by Elk-based applications which contribute their own main() and need to be ``in control'' on startup.

6.1.  Linking the Scheme Interpreter with Extensions  

      A shell script linkscheme (installed as ``lib/linkscheme'') simplifies combining the Scheme interpreter with a number of--user-supplied or predefined--extensions statically. This script is called with the name of the output file (the resulting executable) and any number of object files and libraries. It basically links the object files and libraries with ``standalone.o'' and supplies any additional libraries that may be required by the interpreter. In general, this can be done just as well by calling the linker or compiler directly, but linkscheme also takes care of additional processing that needs to be performed on at least one platform (currently AIX).

      To create an instance of Elk including the Xlib, Xt, and Xaw extensions, linkscheme would be used as follows (again assuming you have installed the software under ``/usr/elk''):

% cd /usr/elk
% lib/linkscheme x11scheme runtime/obj/xt.o runtime/obj/xaw/*.o \
     -lXaw -lXmu -lXt -lSM -lICE -lX11 -lXext

      The exact form of the libraries depends on your platform and X11 version; for example, additional options may be required if X11 is not installed in a standard location at your site. xlib.o is the Xlib extension, xt.o is the X toolkit intrinsics (Xt) extension, and the subdirectory xaw holds the object files for all the Athena widgets. The executable x11scheme can now be used to run arbitrary X11 applications using the Athena widgets without requiring any runtime loading of object files belonging to the X11 extensions:

% x11scheme
> (load '../examples/xaw/dialog.scm)
[Autoloading xwidgets.scm]
[Autoloading xt.scm]
[Autoloading siteinfo.scm]
...

      In the same way, linkscheme can be used to link the Scheme interpreter with any new, user-supplied extensions, with parts of an Elk-based application, or with any combination thereof.

6.1.1.  Automatic Extension Initialization  

      When linking Elk with extensions, it is not necessary to add calls to the extension initializers to the Scheme interpreter's main() function and recompile the interpreter; all extensions are initialized automatically on startup. To accomplish this kind of automatic initialization, Elk scans its own symbol table on startup, invoking any ``elk_init_'' functions and C++ static constructors, in the same way the symbol table of object files is scanned when they are dynamically loaded. Extension finalizers and C++ static destructors are saved for calling on exit. Automatic extension initialization only works if

      The performance overhead caused by the initial scanning of the symbol is small; the program's symbol table can be read or mapped into memory efficiently (it it has not been automatically mapped into the address space by the operating system in the first place).

6.2.  Linking the Scheme Interpreter with an Application  

      Elk-based applications that have their own main() are linked with the Scheme interpreter installed as module.o which, unlike standalone.o, does not export a main() function. No special linkscheme script is required to link with module.o; application writers usually will add ``/usr/elk/lib/module.o'' (or whatever the correct path is) to the list of object files in their Makefile. To simplify linking with Elk, a trivial script ldflags (which lives in ``lib'' along with linkscheme) is supplied that just echoes any additional libraries required by the Scheme interpreter. Application developers may use ldflags in their Makefiles.

      As module.o does not have a main() entry point, an application using it must initialize the interpreter from within its own main(). This is done by calling . Elk_Init():

void Elk_Init(int argc, char **argv, int init_flag, char *filename);

      Elk_Init() is only defined by module.o and is essentially a ``wrapper'' around the Scheme interpreter's main(). argc and argv are the arguments to be passed to the Scheme interpreter's main(). These may or may not be the calling program's original arguments; however, argv[0] must be that from the calling program in any case (because its address is used by Elk to determine the program's stack base). If init_flag is nonzero, the interpreter scans its symbol table to invoke extension initializers as described in @(ch-autoinit). C++ static constructors, however, are never invoked by module.o (regarless of init_flag), because they are already taken care of by the runtime startup in this case. If filename is nonzero, it is the name of Scheme file to be loaded by Elk_Init().

6.2.1.  An Example ``main()'' Function  

      Figure @(main) shows a realistic (yet somewhat simplified) example main() function of an application using Elk.


char *directory;

int main(int ac, char **av) {
	char **eav;
	int eac = 1, c;

	Set_App_Name(av[0]);
	eav = safe_malloc((ac+2+1) * sizeof(char *));    /* ac + -p xxx + 0 */
	eav[0] = av[0];
	while ((c = getopt(ac, av, "gh:o")) != EOF) switch (c) {
		case 'o':
			process option...
		case 'g':
			eav[eac++] = "-g"; break;
		case 'h':
			eav[eac++] = "-h"; eav[eac++] = optarg; break;
		case '?':
			usage(); return 1;
	}
	if ((directory = getenv("APP_DIR")) == 0)
		directory = DEFAULT_DIR;
	eav[eac++] = "-p";
	eav[eac] = safe_malloc(strlen(directory) + 11);
	sprintf(eav[eac++], ".:%s/elk/scm", directory);
	eav[eac] = 0;
	Elk_Init(eac, eav, 0, 0);

	initialize application's modules...

	boot_code();

	application's main loop (if written in C)
	...
Figure 1: Example main() of an Elk-based application (simplified)


      The code shown in the example must construct a new argument vector to be passed to Elk_Init(), because the application has command line options of its own (just -o in the example). Two Elk-options (-g and -h) are handed to Elk_Init() if present, so that a mixture of Elk-specific and application-specific options can be given (see the manual page for the Scheme interpreter for the meaning of Elk's options). (safe_malloc() is assumed to be a wrapper around malloc() with proper error-checking.) Set_App_Name() is provided by Elk and is called with a name to be displayed in front of fatal error messages by the interpreter.

      When all the options have been parsed, an additional option -p is synthesized to provide a minimal initial load-path for Elk. This load-path consists of the current directory and a subdirectory of the directory under which the application expects its files that are needed during runtime. An environment variable can be used to set this directory. Defining a load-path like this has the benefit that a minimal, self-contained Elk runtime environment (e.g. a toplevel and the debugger) can be shipped with binary distributions of the application so that users are not required to have Elk installed at their sites.

      When Elk has been initialized by calling Elk_Init(), the application may initialize all its other modules and finally load an initial Scheme file that ``boots'' the Scheme part of the application (which may involve loading further Scheme files). This initial Scheme file may be quite simple and just define a few functions used later, or it main contain the application's entire ``driving logic'' or interactive user-interface. This is accomplished by a function boot_code() which may as simple as this:

void boot_code(void) {
	char *fn = safe_malloc(strlen(directory) + 30);

	sprintf(fn, "%s/scm/app.scm", directory);
	Set_Error_Tag("initial load");
	Load_File(fn);
	free(fn);
}

      Load_File() is defined by Elk and loads a Scheme file whose name is supplied as a C string. Set_Error_Tag() may be used by extensions and applications to define the symbol that is passed as the first argument to the standard error handler when a Scheme error is signaled (see section @(ch-error)).

6.3.  Who is in Control?  

      When an application's object files are loaded into the interpreter dynamically or are linked with the interpreter using linkscheme, control initially rests in the interpreter. In contrast, when the application is linked using module.o and Elk_Init() as shown in the previous section, it defines its own main() function, and hence the application is ``in control'' on startup.

      From a technical point of view, it does not really make a difference whether control rests in the interpreter or in the application initially. In the first case, the main ``driving logic'' (or ``main loop'') of the application can simply be wrapped in a Scheme primitive which is then called by the Scheme toplevel on startup to pass control back to the application, if this is desired. In any case, control usually changes frequently between the Scheme interpreter and the actual application anyway--the Scheme interpreter invokes callback functions or Scheme primitives provided by the application, which may in turn invoke Scheme procedures or load Scheme files, and so on.

      The Tcl-like style of use, where control rests in the C-part of the application most of the time, and where this C code ``calls out'' to the interpreter occasionally by passing it an extension language expression or a small script, is not typical for Elk. It is supported, though; Elk provides a simple extension to pass a Scheme expression to the interpreter as a C string and receive the result in the same form, similar to what Tcl_Eval() does in Tcl (see section @(ch-funcall)). In a typical Elk-based application the extension language serves as the ``backbone'' of the application: the application's driving logic or main loop is written entirely in Scheme, and this Scheme code calls out to the application's C layer, using the data types, primitives, and other callbacks exported to the extension language by the application. With the help of the X11 extensions, the entire (graphical) user interface of an application can be written in Scheme easily; control can then passed to the application's C/C++ layer whenever an Xt callback is triggered. In this case, the application's ``main loop'' consists of a call to the Scheme primitive corresponding to the X toolkit function XtAppMainLoop() (the main event dispatch loop).


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