Next Previous Contents

4. Type Mappings

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 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.

4.1 Automatically Generated Type Mappings

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.

4.2 Basic Mapping Functions

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)

4.3 Guaranteed-Size Mappings

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)

4.4 References

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);

Using S-Lang References Properly

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.

4.5 Mappings For Aggregate Types

Pointers

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.

Pointer Type Hierarchy

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.

To Code or Not To Code?

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.

Structures

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:

  1. In the present implementation additional manual coding is required to define the structure layout used by the SLang_push_cstruct() and SLang_pop_cstruct() routines.
  2. It is more suitable for flatter structures, i.e. those which do not deeply nest other structures as fields.

File Handles

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.

4.6 Opaque Types

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.

Initializers and Finalizers

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.

Default Mappings

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()

4.7 Defining New Type Mapping Functions

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.


Next Previous Contents