It is important to understand that in order for a generator to emit
useful wrappers it needs to know how to map the types it encounters,
while parsing function signatures,
to the target language. The chief means through which SLIRP does this
for S-Lang is by consulting a type mapping table.
One element of this table, for example, indicates how a S-Lang variable
of Integer_Type should be passed to a C function expecting an int
argument.
At startup this table contains entries for all of the basic C types
(in the parlance of Kernighan and Ritchie, 1988), as well as arrays of and pointers (and C++
references) to them. It may be extended at runtime either
slirp_map_*
family of functions described below.SLIRP will map most types transparently, so even though a wide range of type mapping functions are provided in practice there should be little need for your interface file to explicitly use them. Explicit mappings are most appropriate for wrapping large libraries which define many types (e.g. Gtk and its dependencies), and with autotyping disabled, so as to yield the greatest control over what will be wrapped. Another common use is to register initializers or finalizers for opaque types, to automate memory management details as variables enter or leave scope.
As noted earlier, all basic C types (int, double, etc) are
automatically mapped to the analogous built-in S-Lang type. SLIRP also
attempts to map all type definitions it encounters to the most appropriate
S-Lang type, and will even create new S-Lang types when necessary.
For example, each enumerant within a typed enumeration will be mapped
to Integer_Type, while mappings for "simple" definitions of the form
typedef existing_type new_type;
are simply clones of the type already mapped to existing_type.
Unless otherwise instructed, SLIRP maps almost everything else to opaque types. This is described in more detail in the following sections, but in general opaque mappings take one of two forms: types with known definitions and types with unknown definitions. For example, the type
typedef struct { double p[3]; unsigned long id; } KParams;
defined in the beginning of ksink.h would be considered known when
the prototypes
KParams* ksink_params_new (unsigned long id, double params[3]);
void ksink_params_print (KParams *p);
are encountered later during the wrapping of kitchensink. A unique
opaque KParams S-Lang type would be created to wrap KParams*
instances from C scope, and both functions would be wrapped accordingly.
In contrast, suppose at the outset of a bindings project you elect
to wrap only a fraction of a library whose public interface is
specified in multiple header files, say pub1.h, pub2.h, ... pubN.h;
the SLgtk module, in fact, was developed in exactly this manner.
In this case you would invoke SLIRP on a subset of those headers,
say pub3.h and pub5.h, making it possible for function
signatures within each to refer to types defined only in, say,
pub1.h or pub2.h. Since the formal definitions
of those types would be unknown to SLIRP when parsing the prototypes
of said functions,
the best it can do is map those unknown types to void_ptr. This tactic
permits larger portions of libraries to be wrapped automatically, with
less interface file writing. The -noautotype option offers a
stricter alternative, by instructing SLIRP to entirely avoid wrapping
any function whose signature refers to an unknown type.
The functions given in this section can be used to establish mappings to types defined as equivalent to the built-in types provided by C. For example, a C type definition of
typedef long glong;
would be mapped as follows:
slirp_map_long("glong");
As noted in the preceding section, SLIRP will automatically establish
type mappings for such typedefs, so in practice the basic type mapping
functions should rarely be used. It does no harm for you to include
such within your interface file, however, and doing so can actually be
a convenient way to override the default mapping (especially for types
which specifically require a fixed number of bytes).
All of the mapping functions have a void return type, and in each the
the ctype parameter is a string denoting the name of the type
definition to map. Note that in the following list the first two functions
map to single-byte types, while the last maps to char* strings.
slirp_map_char(ctype) slirp_map_uchar(ctype)
slirp_map_short(ctype) slirp_map_ushort(ctype)
slirp_map_int(ctype) slirp_map_uint(ctype)
slirp_map_long(ctype) slirp_map_ulong(ctype)
slirp_map_float(ctype) slirp_map_double(ctype)
slirp_map_long_long(ctype) slirp_map_ulong_long(ctype)
slirp_map_string(ctype)
Variants of these which enforce 16-, 32-, or 64-, or 96-bit size guarantees are as follows:
slirp_map_int16(ctype) slirp_map_uint16(ctype)
slirp_map_int32(ctype) slirp_map_uint32(ctype)
slirp_map_int64(ctype) slirp_map_uint64(ctype)
slirp_map_float32(ctype) slirp_map_float64(ctype)
slirp_map_float96(ctype)
C passes all arguments by value, so if you want a function call to change the value of a given variable you must pass it the value of a pointer, or reference, to that variable. Reference typemaps can be added with
slirp_map_ref(ctype)
Note that SLIRP implicitly creates a reference mapping, such as
slirp_map_ref("int*");
for all basic C types, as well as types mapped to basic C types. This
allows the kitchensink function
void ksink_set_ref_i (int *i) { *i = -9191; }
to be automatically wrapped for use in S-Lang as
variable i = 111;
ksink_set_ref_i(&i);
vmessage("After ksink_set_ref the new value of i is: %d",i);
It is important to understand that the S-Lang C api only allows references
to pass information in one direction: from a function wrapper to
a S-Lang variable. A S-Lang reference cannot be used to pass the value
of a S-Lang variable into a C function. For example, the default
SLIRP wrapper for the kitchensink function
void ksink_swap_double(double *i, double *j)
{
double tmp;
if (i == NULL || j == NULL) return;
tmp = *i;
*i = *j;
*j = tmp;
}
is effectively unusable, since S-Lang calls such as
variable i = 3, j = 4;
ksink_swap_double(&i, &j);
will yield undefined results. Using the -refscalars switch
at generation time would permit usages such as
ksink_swap_double(i, &j);
(after which both i and j equal 3), but this addresses only
part of the problem since calls such as
ksink_swap_double(i, j);
would result in neither value being swapped. Therefore it is
preferable in these situations to either use annotations or
craft the wrapper entirely by hand.
In this section we expand upon a comment made in section Expectations, regarding the inability of any code generator, by default, to properly map all constructs from C to their "most natural" form in some target scripting language. To see why, consider a function with signature
char** account_get_users(Account *a);
Here Account is an opaque structure, whose layout and content are
implementation details hidden from the programmer. By virtue of
autotyping --- or, when it's turned off, through the use of an
opaque mapping (described below) such as
slirp_define_opaque("Account") --- SLIRP can easily emit
code to create Account* instances in S-Lang scope and pass them
back to C routines.
However, the char** return value is another matter. The idiom is
understood to convey that account_get_users() returns an array of
strings, but of what size? The number of elements pointed to by the
return value is unspecified in the declaration, which renders SLIRP
unable to map the return value to a proper S-Lang array of
String_Type, since to create an array in S-Lang one must indicate
its size. Moreover, in principle the return value might not denote
an array at all, but rather a pointer to a char* whose value the
C caller is free to modify. Again, the absence of disambiguating
clues in the prototype prevents SLIRP from adopting a definitive
interpretation. The same would hold true for return values of type
int*, float*, and so forth.
Left to its own devices the best SLIRP can do when it encounters a
return value denoting an array of indeterminate size is pass it to
S-Lang scope as a pointer, in this case of string_ptr type.
This enables the C char** to exist as a S-Lang variable and be passed
back to other C routines which expect an char**, but --- since
pointers are opaque --- not directly manipulated in S-Lang
scope. For example, attempting to dereference the pointer with the
@ operator or determine the value of the ith element using
[i] would both signal an error.
The complete hierarchy of pointer types provided by SLIRP is as follows:
void_ptr
|
+-string_ptr
|
+-uchar_ptr
|
+-short_ptr
|
+-ushort_ptr
|
+-int_ptr
|
+-uint_ptr
|
+-float_ptr
|
+-long_ptr
|
+-ulong_ptr
|
+-double_ptr
|
+-file_ptr
|
+-opaque_ptr
The base void_ptr type corresponds to generic pointers, such as
void*. The next 11 wrap pointers to the familiar C types,
while the last marks pointers to user-defined opaque types (see next
section). The derived pointer types may be cast to and from the
void_ptr type, but not each other.
Another option for the developer would be to forego the pointer types
automatically generated by SLIRP in favor of manually coding a wrapper.
For example, suppose the documentation for
account_get_users() noted that its return value is, in fact, an
array of strings, and that its final element is guaranteed to be NULL
A hand-crafted wrapper would then be able to loop over the array until
the NULL is encountered, use the final loop index value to fabricate
an appropriately sized String_Type array, and pass the result
back to S-Lang scope.
SLIRP can be instructed to map structures from C scope directly to proper S-Lang structures. This is done for the GdkColor and GdkRectangle types within the SLgtk package, for example, by using
slirp_map_struct("GdkColor*");
slirp_map_struct("GdkRectangle*");
Structure mappings provide an advantage over opaque mappings (see next section) in that they allow structure internals to be inspected or modified at runtime, but there are presently two tradeoffs with this approach:
SLang_push_cstruct() and SLang_pop_cstruct() routines.
S-Lang provides two kinds of handles through which the user may
manipulate files: the FD_Type returned by the open intrinsic,
and the File_Type returned by the fopen and popen intrinsics.
These types are wrappers around the integer file descriptor defined
by POSIX and the FILE* pointer defined by the C stdio library,
respectively.
SLIRP can generate code which permits FD_Type variables to be passed to
wrapped functions in place of integer file descriptors. As described
in section
Builtin_Input_Mappings, however, these transformations
are not implicit, but rather require that you add an extra line or
two to your interface file.
In contrast, SLIRP will always permit a File_Type variable to be passed
to a wrapped function in place of a FILE* pointer. Keep in mind,
though, that files opened by the S-Lang fopen() or popen()
intrinsic functions should only be closed by the
corresponding S-Lang fclose() or pclose() intrinsics.
This stems from the fact that S-Lang maintains additional state within
a File_Type variable, and this state will not properly reflect that a file
has been closed when non-intrinsic wrapper functions are used. S-Lang
may then attempt to manipulate the file again (e.g. to close it when
the File_Type variable goes out of scope), resulting in undefined behavior.
Finally, as noted above SLIRP also provides an opaque file_ptr, instances
of which will be created when wrapped functions return a FILE*. The
resulting file_ptr variable may then be passed to any other wrapped function
in place of FILE*, but may not be passed to S-Lang intrinsic functions
in place of File_Type.
SLIRP provides one additional mechanism for using C aggregate types in S-Lang, namely through the opaque mapping functions:
slirp_define_opaque(slang_type_name [, parent [, finalizer [, initializer]]])
slirp_map_opaque(ctype [,slang_type_name])
Opaque types are so named because, while one can create, pass around,
and even destroy variables instantiated from such a type, one cannot
modify, or even inspect, their internals. In fact the only information
which may be gleaned at runtime from an opaque variable instance is
type metadata (e.g. the type name and class id). The expert S-Lang
programmer will instantly recognize the
equivalence between the notion of SLIRP opaque types and the
SLang_MMT_Type defined by the S-Lang C interface. The new terminology
is used within SLIRP only because it is felt that opaque is a
more familiar term than mmt, and so will convey more information
more quickly to more users.
Opaque variables in S-Lang scope are usually just wrappers around pointers to structures or objects defined and instantiated in C scope. As a case in point again consider the structure type
typedef struct { double p[3]; unsigned long id; } KParams;
defined by kitchensink. When a S-Lang script calls ksink_params_new
the result would be returned to S-Lang wrapped as an opaque type, and
later unwrapped when specified as an argument to the wrapper of a
function expecting a KParams* (such as ksink_params_print).
Normally, for each structured type definition T it encounters
SLIRP automatically issues a slirp_define_opaque(T, NULL,
"free") call so that pointers to T may be used in S-Lang
scope as uniquely typed opaque variables. This default mapping also
specifies that T
is a descendant of no other type (parent=NULL) and that when instances
of T go out of S-Lang scope that free be used to deallocate
the memory consumed.
You can instruct SLIRP to not perform this default mapping by either turning autotyping off or by explicitly specifying a mapping for T within your interface file. For example, consider the following sequence of calls taken from the SLgtk interface file:
slirp_define_opaque("GtkOpaque");
slirp_map_opaque("gpointer");
slirp_map_opaque("gconstpointer");
The first line causes SLIRP to fabricate a definition for a unique
S-Lang type named GtkOpaque, and emit code to install that type
when the SLgtk module is imported. The second and third parameters
are omitted in the call, which means that the type will have no
parent, no initializer, and no finalizer.
The second and third lines then map the C types gpointer and
gconstpointer to the new GtkOpaque S-Lang type. With this
typemap SLIRP will be able to automatically generate wrappers, when
later processing Gtk header files, for functions whose signatures
contain either C type. Without such a typemap SLIRP would otherwise
ignore such functions. Now consider
slirp_define_opaque("GtkTreePath","GtkOpaque","gtk_tree_path_free");
As above, this call fabricates a new opaque S-Lang type, GtkTreePath,
and ensures that code will be emitted to install the type when the
module is imported. Unlike above, however, SLIRP will consider this
new type a descendant of an existing type, namely GtkOpaque.
Moreover, when instances of the type go out of S-Lang scope the
opaque destructor will call gtk_tree_path_free to finalize
the underlying C instance.
The initializers and finalizers mentioned above are optional helper functions which the programmer can direct SLIRP to call when opaque variables come into S-Lang scope (are created) or go out of S-Lang scope (are destroyed), loosely analogous to constructors and destructors within object-oriented languages such as C++ and Java. Both are pointer types declared as
void (*INITIALIZER) (void*);
void (*FINALIZER) (void*);
and usually refer to functions within either the api of the library
being wrapped or the hand-crafted portion of the glue layer. Typically
a finalizer is called to free resources allocated to the C variable which
the opaque wraps; this can be useful, for example, to free fields
within a wrapped C structure that were malloc-ed when it was
instantiated, thus avoiding memory leaks. The default finalizer used
by SLIRP, when automatically mapping pointers to structured types
as opaque S-Lang types, is the C free function.
Note that each call to slirp_define_opaque() also creates a typemap
between the new S-Lang type and a C type whose name is the concatenation
of the S-Lang type name with an asterisk. For example, an invocation
such as
slirp_define_opaque("GtkObject","GObject","slgtk_object_destroyer");
would implicitly issue the call
slirp_map_opaque("GtkObject*","GtkObject");
This simplifies SLIRP interface files by eliminating the redundancy
of having each opaque type definition followed immediately by the
obvious C type mapping.
Finally, note that each opaque type defined becomes the default
S-Lang type for subsequent opaque mappings. SLIRP uses this default
to assign a S-Lang type when one is omitted in an opaque mapping,
which simplifies the SLIRP interface file by allowing the programmer
to specify a type only when it is explicitly necessary. This tactic
also increases the performance of the code generator, since the
S-Lang interpreter needs to parse fewer tokens. The default remains
in effect until the next call to either slirp_define_opaque()
or slirp_set_opaque_default()
For completeness we note that the lowest-level function called by all mapping functions is:
slirp_map (ctype,gluetype,mnemonic,typeid,typeclass [,freer])
In principle this could be used to write custom type mapping functions,
but in practice there should be little need for your interface file to
call it explicitly.