Object files (compiled C/C++ code) are loaded by means of the standard load primitive of Scheme, just like ordinary Scheme files. All you need to do is to compile your C or C++ source file, apply the makedl script that comes with the Elk distribution to the resulting object file, and load it into the interpreter or application. makedl prepares object files for dynamic loading (which is a no-op on most platforms) and combines several object files into one to speed up loading; arguments are the output file and one or more input files or additional libraries (input and output file may be identical):
% cc -c -I/usr/elk/include file.c % /usr/elk/lib/makedl file.o file.o % scheme > (load 'file.o) >
Elk does not attempt to discriminate object code and Scheme code based on the files' contents; the names of object files are required to end in ``.o'', the standard suffix for object modules in UNIX. Scheme files, on the other hand, end in ``.scm'' by convention. This convention is not enforced by Elk--everything that is not an object file is considered to be a Scheme file. A list of object files may be passed to the load primitive which may save time on platforms where a call to the system linker is involved.
Loading object files directly as shown above is uncommon. Instead, the Scheme part of a hybrid extension usually loads its corresponding object file (and all the other files that are required) automatically, so that one can write, for example,
When an object file is loaded, unresolved references are resolved against the symbols exported by the running interpreter or by the combination of an application and the interpreter (the base program). This is an essential feature, as dynamically loaded extensions must be able to reference the elementary Scheme primitives defined by the interpreter core and all the other functions that are available to the extension/application programmer. In addition, references are resolved against the symbols exported by all previously loaded object files. The term incremental loading is used for this style of dynamic loading, as it allows building complex applications from small components incrementally.
Dynamically loadable object files usually have unresolved references into one or more libraries, most likely at least into the standard C library. Therefore, when loading an object file, references are resolved not only against the base program and previously loaded object files, but also against a number of user-supplied load libraries. The X11 extensions of Elk, for instance, need to be linked against the respective libraries of the X window system, such as libX11 and libXt. These load libraries can be assigned to the Scheme variable load-libraries which is bound in the top-level environment of Elk. Typically, load-libraries is dynamically assigned a set of library names by means of fluid-let immediately before calling load. For example, the Xlib extension (xlib.scm) contains code such as
(fluid-let ((load-libraries (string-append "-L/usr/X11/lib -lX11 " load-libraries))) (load 'xlib.o))
When loading an object file, Elk scans the file's symbol table for the names of extension initialization functions or extension initializers. These extension initializers are the initial entry points to the newly loaded extension; their names must have the prefix ``elk_init_'' (earlier the prefix ``init_'' was used; it was changed in Elk 3.0 to avoid name conflicts). Each extension initializer found in the object file is invoked to pass control to the extension. The job of the extension initializers is to register the Scheme types and primitives defined by the extension with the interpreter and to perform any dynamic initializations.
As each extension may have an arbitrary number of initialization functions rather than one single function with a fixed name, extension writers can divide their extensions into a number of independent modules, each of which provides its own initialization function. The compiled modules can then be combined into one dynamically loadable object file without having to lump all initializations into a central initialization function.
In the same manner, extension can define an arbitrary number of extension finalization functions which are called on termination of the Scheme interpreter or application. The names of finalization functions begin with ``elk_finit_''. Extension finalization functions are typically used for clean-up operations such as removing temporary files.
The extension initializers (as well as the finalizers) are called in an unspecified order.
In addition to calling extension initialization functions, the load primitives invokes all C++ static constructors that are present in the dynamically loaded object file in case it contains compiled C++ code. Likewise, C++ static destructors are called automatically on termination. The constructors and destructors are called in an unspecified order, but all constructors (destructors) are called before calling any extension initializers (finalizers). Elk recognizes the function name prefixes of static constructor and destructor functions used by all major UNIX C++ compilers; new prefixes can be added if required.