Next Previous Contents

6. #argmap Annotations

Formally, an #argmap specifies how a sequence of N >= 1 function arguments in C map to a sequence of M <= N arguments specified to a S-Lang function call. In the common case N = M = 1, resulting in a one-to-one correspondence between C functions and their S-Lang wrappers. The case where N > 1 is referred to as a multi-argument map and, as shown in the examples, is commonly used to collapse a sequence of C arguments into a shorter sequence of of S-Lang arguments. The argmap grammar is given by

             #argmap ( method [,qualifier_list] ) parameter_list [( variable_declaration_list )]

                 code_fragment

             #end

More details are given in the grammar reference, but in the context of the first argmap from the previous chapter

        #argmap(in, which=1) (float *arr, int)
           $2 = ($2_type) $1_dim0;      /* float* argmap */
        #end

Preprocessor tokens may not appear within the body of an annotation, which should be viewed as a fragment of C code subject to the substitution rules described earlier. After substitution these fragments are injected directly into the wrapper during code generation. No further validation is performed upon the generated code, making it entirely possible to write annotations which yield wrappers that will not compile.

The method keyword of each argmap governs where in the generated wrapper the argmap code fragments will be placed. For instance, the body of an in argmap would appear within the Argument_Marshalling_Block of a wrapper, while the code from a final argmap would be injected into its Return_Block.

6.1 in Method

The #argmap(in) form allows you to customize the code generated for passing inputs from S-Lang to wrapped functions. By default all arguments expected by a wrapped function must be supplied to its wrapper at call time. The optional which=selection qualifier may be used to change that, by identifying which parameters of a multi-argument map must be specified in the S-Lang call sequence (and, by implication, which should be omitted). The selection value is a S-Lang array index expression, and gives rise to several forms for the qualifier:

Since function parameters are numbered from 1 the i, j, k, ... here may only take on positive integral values. A compact way of omitting all parameters from the S-Lang call sequence is to use an omit qualifier. For example, imagine you would like to ensure that a function prototyped as

                arg_dropper(unsigned long int ul);
be called from S-Lang only with the value 112233. This could be achieved by forcing the function to be called from S-Lang with zero arguments, via
        #argmap(in, omit) unsigned long int ul
           $1 = 112233;
        #end
and letting the wrapper pass on the desired value.

Built-in in Maps

As a convenience SLIRP provides a number of input maps. As discussed here, some of these will be applied automatically by SLIRP, while others will need to be specified manually within your interface file.

File Descriptors

        #argmap(in, proxy=SLFile_FD_Type) int FD_PROXY
           if (-1 == SLfile_get_fd (proxy, &$1)) {
                SLang_verror(SLEI, "could not assign file descriptor proxy");
                return;
           }
        #end
The FD_PROXY annotation allows S-Lang FD_Type file handles, which are opaque, to be passed directly to compiled routines expecting an integer file descriptor. This permits natural usages such as
        variable fd = open("/some/dir/some_file",O_RDONLY);
        ksink_close(fd);
where the corresponding kitchensink function is prototyped as
        extern  void  ksink_close (int fd);
The int parameter in this annotation is explicitly named FD_PROXY so that it will not be applied to just any int argument within any function signature. That is, because integers are a common type and we cannot reliably expect all libraries to employ the "int fd" idiom, your interface file must explicitly assert when it should be applied. For the ksink_close function above this is achieved with
        #copy int FD_PROXY { int fd }

Generic Pointers

        #argmap(in, proxy=SLang_Array_Type) void* ARRAY_2_VOIDP
           $1 = proxy->data;
        #end

        #argmap(in, proxy=SLang_Any_Type) void* ANY_2_VOIDP
           $1 = proxy;
        #end
The ARRAY_2_VOIDP and ANY_2_VOIDP annotations facilitate the passing of arrays or arbitrary objects, respectively, from S-Lang to compiled routines expecting a void* pointer. As with the file descriptor mapping above, these mappings are explicitly named as a precautionary measure, so as to prevent them from being applied to just any function with a void * argument. As an example consider the HDF5 function
        herr_t H5LTmake_dataset( hid_t loc_id, const char *dset_name, 
                         int rank, const hsize_t *dims, hid_t type_id,
                         const void *buffer );
In C this function can be called with an arbitrarily-typed array as the last parameter (e.g. float*, double*, etc). By default, however, SLIRP will wrap this function as if the last argument were an opaque pointer instance (absent any hints what else can it do?) One way to use the builtin argmap to change the generated wrapper would be to copy it with
        #copy void* ARRAY_2_VOIDP { const void* }
so that any function in the HDF5 api which expects a const void* argument would automatically support the C idiom of passing in arbitrarily-typed arrays.

C++ string Objects

These annotations allow the use of S-Lang strings in place of C++ string objects. They support both scalars and arrays, and will be applied transparently.

        #argmap(in, proxy="char*") string
           $1 = proxy;
        #end

        #argmap(in, proxy=SLang_Array_Type) string*
           {
           int i, l;
           char **arr;
           if (proxy->data_type != SLANG_STRING_TYPE) {
                SLang_verror(SL_USAGE_ERROR, (char*)"String array is required here");
                return;
           }
           arr = (char**)proxy->data;
           l = proxy->num_elements;
           $1 = new string[l];
           for (i=0; i<l; i++)
                $1[i] = string(arr[i]);
           }
        #end
If the C++ interface you're wrapping contains many functions or methods with string array arguments, then to avoid duplicative bloat you may wish to move the content of the string* annotation into a function (defined, e.g., within an #inline_c directive) and override the built-in version in your interface file with an annotation whose body simply calls that function. A similar strategy is employed by the built-in #retmap for NT_STR_ARRAY.
        

6.2 out Method

The #argmap(out) form allows you to customize the code generated for transferring output from wrapped functions back to S-Lang scope. It's most commonly used to drop a single argument (typically a reference) from the function inputs, in favor of having its value returned on the stack instead. This would allow, for example, a function prototyped as

        void ksink_mult2 (double op1, double op2, double *result);
to be called more naturally from S-Lang as
        result = ksink_mult2(333, 3);
instead of
        variable result;
        ksink_mult2(333, 3, &result);
as would be required by the default wrapper. The latter form can be cumbersome, especially for interactive use (e.g. within ISIS http://space.mit.edu/cxc/isis/, which presents a Matlab(tm)-style command line prompt for scientific analysis), but with an output annotation such as
        #argmap(out) double *result
           $return;
        #end
SLIRP is able to transform the default generated wrapper
        static void sl_ksink_mult2 (void)
        {
           double arg1;
           double arg2;
           double* arg3;
           Slirp_Ref arg3_ref = Slirp_ref_init(SLANG_DOUBLE_TYPE, sizeof(double), arg3);
        
           if (SLang_Num_Function_Args != 3 ||
                pop_array_or_ref( &arg3_ref) == -1 ||
                SLang_pop_double(&arg2) == -1 ||
                SLang_pop_double(&arg1) == -1 )
                {Slirp_usage_err("ksink_mult2(double,double,double_ptr)"); return;}
        
           ksink_mult2(arg1, arg2, arg3);
           (void) Slirp_ref_finalize(&arg3_ref);
        }
into
        static void sl_ksink_mult2 (void)
        {
           double arg1;
           double arg2;
           double arg3;
        
           if (SLang_Num_Function_Args != 2 ||
                SLang_pop_double(&arg2) == -1 ||
                SLang_pop_double(&arg1) == -1 )
                {Slirp_usage_err("double = ksink_mult2(double,double)"); return;}
        
           ksink_mult2(arg1, arg2, &arg3);
           SLang_push_double(arg3);
        }
and effectively make ksink_mult2 a duplicate of ksink_mult given in the opening example. This also allows using the wrapper result as an inlined argument to another function, such as
        printf("The result is %d\n", ksink_mult2(333, 3));
which is simply not possible in its original form. Note that, as is often the case with SLIRP, the same effect could be achieved in other ways. For example, we could have simply used the #copy directive, as described in section copy_example, to reuse the built-in double *OUTPUT annotation:
        #copy double *OUTPUT { double *result }
Alternatively, our outmap could have explicitly pushed its argument onto the stack
        #argmap(out) double *result
           SLang_push_double($1);
        #end
although this approach is not advisable. For one, using the type-specific S-Lang push routine requires more knowledge, on the part of the bindings developer, of the internal S-Lang C api. It is also less general, because its type-specificity prevents the annotation from being reused (again, by #copy) for other types.

Finally, note that the usage=C_string_literal qualifier may be used to override the default Usage: message generated by SLIRP for the mapped argument, and that outmaps support neither variable substitution nor multiple-argument parameter lists.

Built-in out Maps

SLIRP provides a number of built-in output mappings, such as:

        #argmap(out) short *OUTPUT
           $return;
        #end
The effect of this outmap is duplicated for other types by using #copy
        #copy short *OUTPUT { unsigned short *OUTPUT, int *OUTPUT,
                unsigned int *OUTPUT, unsigned *OUTPUT, long *OUTPUT,
                unsigned long *OUTPUT, float *OUTPUT, double *OUTPUT,
                spcomplex *OUTPUT, dpcomplex *OUTPUT}

Each of these are, for the convenience of brevity, also copied to

        #copy short *OUTPUT { short *OUT, unsigned short *OUT, int *OUT,
                unsigned int *OUT, unsigned *OUT, long *OUT, unsigned long *OUT,
                float *OUT, double *OUT, spcomplex *OUT, dpcomplex *OUTPUT}
Note that the spcomplex and dpcomplex types refer to internal types defined by SLIRP to correspond to the single- and double-precision complex types offered by Fortran. As yet there is no default support for C99 complex types.

As an example consider that the two annotations

        #copy long *OUTPUT      { long *result }
        #copy long *OUT         { long *result }
are equivalent: both copy the default long* output mapping to the named argument long *result, so that if SLIRP sees the latter whilst parsing function signatures it will know to remove it from the input argument list of the wrapper function and instead treat it as a return value of the wrapper.

As shown in the next chapter, these may be combined with the #prototype and #copy annotations to simplify or tune the matching of annotations to functions. Note that the complex and doublecomplex mappings refer to the corresponding Fortran types.

6.3 final Method

The #argmap(final) form provides a means of explicitly specifying how arguments should be finalized by a wrapper just prior to its return. This can be useful for bookkeeping work, such as freeing memory allocated within the wrapper or ensuring that an object is properly destroyed. As an example of the latter, suppose a C table I/O library contained the routines

                extern Table*   table_open(char *name);
                extern int      table_close(Table *t);
                extern void*    table_get_column(Table *t, int column);
that were wrapped by SLIRP by mapping Table* to an opaque type
                slirp_define_opaque("Table", NULL, "table_close");
and then called from S-Lang in the usual way
                variable table = table_open("table.dat");
                variable col1 = table_get_column(table, 1);
                variable col2 = table_get_column(table, 2);
If the C table_close routine frees its input Table* pointer (good policy) then after a call
                table_close(table);
in S-Lang scope the opaque table variable would encapsulate freed memory. Subsequent attempts to use it, such as
                table_close(table);
would yield undefined behavior (e.g. SEGV). This can be prevented by executing
                table = NULL;
explicitly after the table_close call or, more elegantly, by having the table_close wrapper transparently achieve a similar effect by nullifying the Table* instance wrapped by the opaque S-Lang table variable. Since it is, again, advantageous to avoid writing such a wrapper entirely by hand one might instead craft annotations such as
        #argmap(final) Table* NULLIFY
           $1_nullify;
        #end

        #prototype
           int   table_close(Table* NULLIFY);
        #end
The first annotation performs the required nullification, and uses a uniquely named parameter list so as to limit its application only to the table_close function. The second annotation redeclares the prototype for this function, allowing it to match the #argmap(final).

6.4 ignore Method

The #argmap(ignore) form provides an alternative to the #ignore directive. Rather than ignoring a function based upon its name, this annotation supports ignoring functions by matching on their argument signatures. It provides a potentially powerful means of ignoring entire groups of functions with a single, simple annotation. For example, when generating pure C bindings for C++ code (-cfront mode) SLIRP provides the builtin annotation

   #argmap(ignore) string*
   #end
which ensures that functions containing string array arguments will not be reflected in the bindings. Note that because ignore maps cause code NOT to be generated, any code fragment within the body of the mapping is ignored, and in fact there is no need to explicitly end the argmap block with #end. That is, the single line
   #argmap(ignore) string*
achieves the same end as above.

6.5 setup Method

The #argmap(setup) form supports the injection of code fragments into a wrapper just prior to argument marshaling, allowing wrappers to be customized to take preliminary action based upon the number of arguments passed, or their types, et cetera.


Next Previous Contents