In the broadest sense SLIRP consumes an interface definition and produces one or more wrapper products. The input interface is specified via one or more source files (C or C++ headers, and/or Fortran source), supplemented with an optional SLIRP interface file. The generated output may take the form of:
By default SLIRP generates S-Lang wrappers; the other output
forms are generated by specifying the -stubs, -cfront,
-make, and -print options, respectively, at invocation.
SLIRP is capable of wrapping most routines defined in most C and Fortran 77 codes. Its C++ support is less comprehensive, but broad enough to generate useful bindings for a variety of C++ codes. Although the code generated by SLIRP has been verified compliant with more modern Fortran compilers (such as gfortran or f95), there is no explicit support for extensions to the language (e.g. objects or modules) which appear in newer versions of the Fortran standard.
There are three conditions under which SLIRP will not wrap a given
function. The first two occur when the -noautotype switch has
been specified on the command line and there is no
For example, since SLIRP does not contain typemaps for function pointers any function whose signature contains one will not be wrapped. SLIRP will also ignore a function
This can be useful when you would prefer to code a wrapper manually rather than accept what SLIRP would generate, or if you'd rather not wrap the function at all. The exact mechanisms for ignoring symbols are discussed in the next section.
Each S-Lang wrapper function has the general form
Function_Declaration
{
Variable_Declaration_Block
Argument_Marshal_Block
Function_Call_Block
Return_Block
}
Wrappers are declared with static linkage, to avoid clashes with
other modules that might define similar symbols. Arguments are
passed from S-Lang scope in one of two ways: via
the function parameter list (for simple scalar types) or within the
argument marshalling block. In the latter case the wrapper is declared
with a void argument list and each argument is explicitly popped
from the S-Lang stack and verified to match in type and number the
arguments required by the wrapped function. The function call block
invokes the underlying routine, while the return block is responsible
for finalization tasks such as deallocating temporary resources and
pushing the return value onto the S-Lang stack.
Through the use of annotations it's possible to automatically construct S-Lang wrappers whose signatures do not match that of the underlying function, by either accepting fewer arguments or returning more values.
By default the argument marshalling block also contains code to
emit Usage: statements when the function has been invoked with
the incorrect number or type of arguments. These serve as useful
reminders of the purpose of the function, for both new and veteran
users of a library, and can often save a trip to the documentation.
See the -nopop runtime switch for more details.
Although many command line options are provided to steer the code generation process, interface files are by far the more powerful means of customizing and extending SLIRP, and their description occupies the bulk of this manual. Interface files are S-Lang scripts, and so may contain any legal collection of S-Lang statements. They are also optional, in the sense that SLIRP does not require one for nominal execution; most bindings projects, however, would likely benefit from their use.
The SLgtk interface file, for example, contains hundreds of type
mappings and ignore directives, among other content. At the other end
of the scale, the kitchensink module could in principle be generated from
an interface file containing only
slirp_define_opaque("KDatum",NULL,"ksink_datum_destroy");
While most SLIRP interface files won't be this brief, generating one
for a first cut module for the typical library will usually take only a
few minutes. Additional interface files are bundled in the examples
subdirectory of the SLIRP distribution.
By default SLIRP looks for an interface file named slirprc in the
current working directory; failing that, SLIRP then attempts to load
the file specified by the environment variable $SLIRPRC. Specifying
the -rc switch at invocation time overrides both of these,
provided the given file is readable.
An interface file may reference other interface files by calling
slirp_include_rc(filename)
This permits modules with obvious dependencies to share common type
definitions and variables, in fashion similar to that of the ANSI C
#include mechanism. Note that this will likely introduce a
link time dependency between the modules, which will have to be
reflected in your distribution (e.g. within a Makefile or project
file).
The function has a void return type, and will signal an error if problems occur while loading the external file. To avoid redundancy and save space, code will not be generated for types loaded through external interfaces files.
As noted earlier, SLIRP does not contain a full C preprocessor, with
the chief omissions being that it does not #include headers at
runtime, nor will it perform substitution upon parameterized (function)
macros. This should go unnoticed by most bindings projects,
especially those whose interface is defined within a single file.
Libraries which define their public interface within multiple header
files, such as Gtk, may be wrapped with SLIRP by specifying each
public header on the command line at invocation time.
SLIRP evaluates the conditional compilation directives #ifdef,
#ifndef, #if, #elif, #else, and #endif, and will avoid wrapping any
content within an exclusion block. The ?, :, and ,
operators are not supported during conditional evaluation, and will
induce the truth value of the entire expression in which they appear
to 0 (false).
#define MacrosSLIRP wraps integral-valued and real-valued #define macros
as Integer_Type and Double_Type constants, respectively. Likewise,
string-valued macros (i.e. text enclosed within quotes) are wrapped
as variables of String_Type. SLIRP adopts a conservative stance for these
wrappings, preferring macros of form
#define identifier value
or combinations thereof which, after substitution, are equivalent to
such. Source file macros can be redefined in an interface file by
using either of
slirp_define_macro(name [, value]);
#define name [value]
Both parameters to the functional form are strings. To define a
string constant using the functional form the value must include
appropriately delimited quotes. Following C, when value is
omitted the macro is defined to be the empty string. Source macros
may be ignored by specifying an #undef directive
#undef name
within an interface file, or by using the methods described in
section
Ignoring. Both the #define and #undef directives are
described further within the chapters on annotations.
#define MacrosMacros whose LHS contains parentheses, and possibly commas, are referred to as parameterized, or function, macros. Although the preprocessor will not substitute such macros when parsing source files, it is possible to wrap them as functions callable from S-Lang scope with
slirp_map_macro(macro_name , "return_type ( argument_list )");
This indicates to SLIRP that the named macro should be interpreted
as if it were a function prototyped as
return_type strlow(macro_name) (argument_list);
While processing enumerations SLIRP will wrap each enumerant
as a S-Lang Integer_Type constant. This allows scripts to utilize the
enumeration in the same manner as would C code, and in many instances
the two will be indistinguishable from one another. For example,
the following S-Lang snippet
variable value = KSINK_BAD;
ksink_print_error(value);
is equivalent to the C code
int value = KSINK_BAD;
ksink_print_error(value);
and in fact both could be reduced to the single line
ksink_print_error(KSINK_BAD);
and used interchangeably.
Sometimes it's desirable to have SLIRP avoid wrapping one or more
functions, macros, or variables. Prior to the introduction of annotations
this was done by adding the name of each individual symbol to an
ignore array, as described in the following subsections. Although
this is still supported, the
preferred method is to use an annotation such as #ignore or #undef,
as they are cleaner and more general. In either case, recall that
because C symbol names are case-sensitive the contents of ignore lists
are as well.
Groups of functions may also be tagged en masse for ignoring, by
matching their argument signatures to an #argmap(ignore) annotation
as described in section
IgnoreMap.
Prior to the introduction of the #ignore annotation a function could
be explicitly ignored by adding its name to the list of
ignored functions within your interface file. This list is
declared by SLIRP as a global array of String_Type
variable ignored_functions = String_Type[0];
that is empty by default. The idiom for extending the list
in this manner is
ignored_functions = [
"name_of_first_function_to_ignore",
"name_of_second_function_to_ignore",
...
"name_of_nth_function_to_ignore",
ignored_functions ];
This approach is deprecated, though, in favor of the #ignore or
#argmap(ignore) annotations.
If you'd like SLIRP to ignore a particular #define
macro, add its name to the ignored_macros list. This is another
String_Type array, initially declared as
variable ignored_macros = String_Type[0];
Rather than explicitly adding new elements to this array, though,
your interface file should utilize either the #ignore or #undef
directives. Finally, the array
variable ignored_variables = String_Type[0];
records the names of those variables defined in the input interface
which SLIRP should not make visible within S-Lang scope. Again,
instead of explicitly manipulating this variable your interface file
should employ the #ignore directive.
NULL and Omitted Arguments
Unlike in other languages, where null is viewed as a value
that may be assigned to pointers of any type, a null in S-Lang
is both a value (NULL) and a type (Null_Type). The
Null_Type is useful, but can require additional care on the part
of language binders. To see why, consider the prototype
void SomeFunc(char *name, double value);
All C compilers should permit this function to be invoked as follows:
SomeFunc(NULL, 5);
(whether the function behaves well in such cases is a different
question). A straightforward mapping of this function to S-Lang would
be registered with a function table entry like
MAKE_INTRINSIC_2("SomeFunc", SomeFunc, SLANG_VOID_TYPE,
SLANG_STRING_TYPE, SLANG_DOUBLE_TYPE)
But this would prohibit the function being called from S-Lang scope as
SomeFunc(NULL, 5);
because the interpreter would complain that the Null_Type of the
first argument cannot be cast to a String_Type. Fortunately there is
a way around this, namely to register the wrapper as though it
accepts zero arguments
MAKE_INTRINSIC_0("SomeFunc", SomeFunc, SLANG_VOID_TYPE)
and then pop the char* and double values explicitly within the body
of the wrapper, at which point their types may be checked and the
NULL values handled accordingly.
While this technique works, it would seem to require additional -- and more important, manual -- coding on the part of the language binder. To automate the generation of such code SLIRP provides the associative array
variable accepts_null_args = Assoc_Type[Any_Type,NULL];
The elements of this array are themselves 1D arrays, whose elements
represent those arguments in the function signature for which
NULL is a valid value. In our case a statement like
accepts_null_args["SomeFunc"] = [1];
would indicate to SLIRP that when it's asked to generate a wrapper
for SomeFunc it should consider NULL a legal value for the
first argument, whatever its type may be. This feature also allows
functions to be called with arguments omitted, since the two statements
SomeFunc( , 5);
SomeFunc(NULL, 5);
are equivalent in S-Lang.
When wrapping a C++ library SLIRP will attempt to wrap all functions,
class methods, constants, enumerated types, and simple #define macros
specified in its public interface. Get/set methods are automatically
generated to wrap simple public fields of a class. Overloaded functions
and methods will be wrapped, and class inheritance relationships will be
honored. Default
values are also supported, although you should be mindful that the only
sensible default value which may be assigned to pointer arguments is NULL.
While this enables the module developer to wrap a good fraction of mainstream C++ code, by no means should it be considered a full treatment of the language. For example, get/set wrappers for public class fields are generated only when those fields are of basic/primitive type (in the parlance of Kernighan and Ritchie, 1988), and a number of more semantically deep features of C++ (such as templates, operators, and friend classes) are completely unsupported.
The -cfront option may be specified on the commandline to generate
standalone C wrappers for a C++ library. The code generated in this case
will make no reference to the S-Lang C api, and is thus suitable for
calling the C++ library directly from C or Fortran. Examples of both
usages are given in the examples/cpp directory.
If a Fortran compiler is detected on the system during configuration
SLIRP will also be able to generate bindings for many Fortran codes.
Simply specify the Fortran source (where files are assumed to end with
either a .f or .F suffix) to SLIRP in the same manner one
would normally specify C/C++ headers. SLIRP allows Fortran and C/C++
to be processed during the same invocation, which simplifies the
creation of mixed-language modules. Several sample modules containing
wrapped Fortran code are given in the examples/fortran and
examples/vec directories.
Implicit typing is supported, as are complex-valued functions and subroutines with complex arguments. In most common cases necessary conversions like transposition and string array unrolling are performed transparently, allowing multidimensional and CHARACTER arrays to be passed back and forth between S-Lang & Fortran without regard to issues like string length or whether arrays are internally laid out in row-major or column-major formats. COMMON blocks will also be wrapped, and can be accessed from S-Lang scope using the functions
get_commblock_list() gives names of all common blocks in module
get_commblock(blkname) retrieves a CommBlock handle to a given block
get_commblock_value(block,varname) retrieves value of named variable in block
set_commblock_value(block,varname,value) assigns value to named variable in block
as well as using struct.field notation. Here are some examples:
linux% cd examples/fortran
linux% slsh
slsh> slsh_append_semicolon(1);
slsh>
slsh> import("fsubs")
This module interfaces to 6 Fortran common blocks.
You may need to invoke the Fortran routine(s) which
define(s) them prior to using their values in S-Lang.
You may use struct.field notation to access or modify
the values of individual common block variables.
Type "help commblock" for more information.
slsh> initcomm("blah")
slsh> com1 = get_commblock("com1")
slsh> com1
Fortran common block with 2 variables:
icom1 Integer_Type
rcom1 Float_Type
slsh> com1.icom1
2
slsh> com1.icom1 = 99
slsh> com1.icom1
99
slsh> print(get_commblock_list)
"com_block2_"
"com4"
"_BLNK_"
"com3"
"com5"
"com1"
slsh> com4 = get_commblock("com4")
slsh> com4
Fortran common block with 4 variables:
comp1 Complex_Type
comp2 Complex_Type[2]
comp3 Complex_Type
comp4 Complex_Type[2]
slsh> print(com4.comp4)
(40 + 50i)
(60 + 70i)
slsh> com4.comp4[0] = -9 - 9i
slsh> print(com4.comp4)
(-9 - 9i)
(60 + 70i)
The main limitation of the COMMON block support is that individual elements
of string arrays & single-precision complex arrays cannot be modified:
slsh> com4.comp2[0] = -6i
Complex_Type Array is read-only
This is because S-Lang does not provide, as of version 2.1.3, a single
precision complex type, so COMMON block variables of this type must
be converted to/from double precision. Similarly,
slsh> com5 = get_commblock("com5")
slsh> com5
Fortran common block with 2 variables:
str String_Type (length 13)
strarr String_Type[3] (length 13)
slsh> com5.strarr[0] = "foo"
String_Type Array is read-only
string arrays are implemented differently in Fortran and S-Lang/C, so
passing them back and forth between the languages also requires
conversion. These conversions are, however, transparently performed
for scalar instances of string and single-precision complex types:
slsh> com4.comp1
(3 + 2i)
slsh> com4.comp1 = 99 - 99i
slsh> com4.comp1
(99 - 99i)
Recall that Fortran call semantics differ from C, in that Fortran passes
all variables by reference (i.e., as pointers), even scalars,
while C passes only by value. The implications of this are that to pass
a scalar variable from C to Fortran one normally needs to use the address
operator, as in:
int c_variable = 5;
some_fortran_function( ..., &c_variable, ...);
As a convenience SLIRP hides this detail from the S-Lang
programmer, enabling Fortran entry points to be invoked from S-Lang
with the same syntax used to invoke C functions. However, as demonstrated
in the sample Fortran module, when writing your own annotations your
prototypes will need to use pointer syntax.
Another convenience afforded by SLIRP is that Fortran functions and
subroutines are invoked from S-Lang scope using exactly the same name
as would be employed in Fortran scope. That is, the S-Lang programmer
needs no knowledge of how the Fortran compiler mangles entry point
names (e.g. upper case, or underscore suffix, etc) in order to call
a function or subroutine from S-Lang scope.
In order to maximize portability SLIRP wraps all Fortran function
calls within a subroutine whose first argument corresponds to the
return value of the function. In general this tactic will not be
visible at the S-Lang layer, although the generated wrappers will
themselves need to be reflected as a build dependency within the
module Makefile. For example, the SLIRP Makefile generation facility
would automatically add sfwrap_foo.f as a dependency for a module
generated from foo.f.
Finally, routines and functions whose signatures contain function pointer arguments (actual arguments which are names, e.g., of external procedures) will not be automatically wrapped.
As an additional convenience SLIRP can also generate "starter" make
files to automate compilation and linking of your module. For example,
given a header file cos.h containing
double cos(double x);
one might generate the module, compile it, and invocation-test the result
in just two simple steps:
unix% slirp -lm cos.h
Starter make file generated to 'Makefile'
unix% make test
cc -fPIC -I. -c cos_glue.c
gcc -shared -o cos-module.so cos_glue.o -lm -lslang -ldl -lm
slsh cos-test.sl
Success!
(see the examples/makef directory for a demonstration).
This process can be initiated or tuned by the -make, -ldflags,
-I, -L, and -l flags, which are described in section
Command_Line_Options and should be reasonably familiar to anyone
conversant with compilation.
SLIRP can be used to generate empty (template) modules, by either specifying an empty header file, such as
unix% touch foo.h
unix% slirp foo.h
unix% ls -l foo_glue.c
-rw-rw-r-- 1 mnoble asc 3393 Nov 10 17:39 foo_glue.c
or by specifying /dev/null as input:
unix% slirp -m template_example /dev/null
unix% ls -l template_example_glue.c
-rw-rw-r-- 1 mnoble asc 3470 Nov 10 17:41 template_example_glue.c
When the -stubs option is specified SLIRP will generate one
callable function with an empty body, or stub, for each entry point
in the input interface. This can be a
surprisingly useful testing feature, since it allows a module to be
generated and nominally exercised without the need to compile or link
in the underlying library or any of its (potentially numerous)
dependencies.
The SLIRP distribution bundles two stub modules, in the examples/stubs
and examples/opengl subdirectories. In the OpenGL module, for
example, stubs for the functions prototyped as
void glVertex2d( GLdouble x, GLdouble y );
GLboolean glAreTexturesResident( GLsizei n, const GLuint *textures, GLboolean *residences);
resemble
void glVertex2d (GLdouble arg1, GLdouble arg2) { }
GLboolean glAreTexturesResident (GLsizei arg1, const GLuint* arg2, GLboolean* arg3)
{
return (GLboolean) 0;
}
Since stub functions are pure C they exhibit no dependency upon S-Lang,
and may thus be employed in wider contexts.
Debugging modules can be difficult, e.g. because breakpoints cannot be set within a module until after it's been loaded. Further, knowing exactly where (in the source code) and when (during process execution) this occurs requires in-depth knowledge of S-Lang library internals, and is virtually impossible to isolate when the S-Lang library has not been compiled for debugging. To address these issues SLIRP offers the -d option, which will embed within your module the slirp_debug_pause() stub.
To activate the stub set the SLIRP_DEBUG_PAUSE environment variable before importing your module. This will cause the parent process to wait before exiting module initialization, during which time you may set breakpoints within the codebase of the module (since its symbol table will have been loaded at that point) or its dependencies. This removes the need for module developers to learn the internals of dynamic loading in S-Lang, and permits the module to be debugged even when the S-Lang library itself has not been compiled for such.
The debugging stub will do nothing if SLIRP_DEBUG_PAUSE is unset in the
environment. When the variable is set, however, if the result of
atoi(getenv(SLIRP_DEBUG_PAUSE))
evaluates to a negative integer N slirp_debug_pause() will sleep
for abs(N) seconds, otherwise the stub will pause indefinitely, awaiting
a keypress in the terminal from which the parent process was launched.