XS, as we mentioned earlier, is an interface definition language. Unlike SWIG, XS concentrates solely on C functions and #define'd constants and does not provide any support for struct or class definitions (although there are plans for doing so in the future). In practice, I haven't missed this support for structures and classes too much because I rarely export data structures, in keeping with encapsulation principles.
The XS approach allows you to modify the XS file and supply glue code (in C) in varying degrees. It is analogous to C or Pascal compilers that allow you to insert native assembly code within a program. This gives a lot of power if you know what you are doing, but requires you to be conversant with the internal Perl API and protocols.
By modifying the XS file, you can create write
function wrappers that take a variable number of input parameters, modify some input parameters (as read does), and return an array of result values. Combine this with the ability to write custom typemaps and modify the Perl module (produced by h2xs), and you have several ways of creating extensions.
Let us take a brief look at XS syntax. Fractal.xs, from our earlier example, looks like this in its most essential form:
#include <mandel.h> MODULE = Fractal PACKAGE = Fractal int draw_mandel (filename,width,height,origin_real,origin_imag,range,depth) char* filename int width int height double origin_real double origin_imag double range double depth
All text preceding a MODULE statement is considered to be raw C code and is sent untranslated into the Fractal.c, the glue code (like the %{ ... %} block in SWIG). An XS module can contain more than one package, but since this is not typical, the MODULE and PACKAGE keywords have the same value. All exportable functions are listed in a special way. The return type comes first, on its own line (you must specify void in the absence of a return type), then the name of the function with a list of parameter names, and, finally, each parameter on a separate line. It is important to keep the "*" along with the type, not the name - you must say char* filename, not char *filename. The next function declaration simply starts after a blank line.
It pays to understand a little bit about the glue code generated by xsubpp. When xsubpp is given the XS snippet shown above, it creates a function called Fractal_xs_draw_mandel (in Fractal.c) with the same signature as the XS declaration. This function translates the arguments supplied in Perl space to the C function's parameters, calls the real draw_mandel function, and finally packages its return value into a Perl value.
XS provides several keywords to either inject your own code at suitable locations inside the generated function or completely replace the generated glue code with your own. For example, you can write typemap functions that handle how Perl arguments get translated to C; you can use the CODE keyword (described later) to specify that you are supplying your own code.
With this brief overview in mind, let us now look at a few of the important aspects of the XS language.
Parameters can have default values but, as in C++, can be applied only to the rightmost parameters:
draw_mandel (file,width,height,orig_real,orig_imag,range,depth=30)
This allows you to optionally skip the last parameter when calling from Perl.
XS allows you to modify parameters before they are given to the real draw_mandel function:
int draw_mandel (filename,width,height,origin_real,origin_imag,range,depth) char* filename int width int height double origin_real double origin_imag double range double depth INIT: if (width > 400) { fprintf (stderr, "Width cannot exceed 400. Truncating.\n"; width = 400; }
The INIT: keyword tells XS to insert the code following it between the argument translation (from Perl to C) and the call to the real function.
In SWIG, you would use a named typemap for the same effect. The XS approach, however, allows you to make a decision based on more than one parameter. For example, if you had to maintain a certain aspect ratio, you would have to look at both width and height and modify one of them. A typemap cannot give you this flexibility because it looks at each parameter in isolation.
Incidentally, the PREINIT: keyword can be used to insert variable declarations; xsubpp puts these declarations ahead of any generated code. Of course, this keyword is not important if you compile the glue code with a C++ compiler, since it allows you to declare variables anywhere in the code.
You can write the glue code yourself if you want. Consider the sin() function in the math library, which requires you to supply the angle in radians. You can create a new function in Perl to accept the angle in degrees using the CODE keyword, like this (the indentation scheme is arbitrary):
double d_sin(angle) double angle CODE: RETVAL = sin(angle * PI / 180); OUTPUT: RETVAL
When xsubpp sees the CODE keyword, it just maps the arguments from Perl data types to C types and leaves you to supply the rest of the code, which means that you have to make the call to the underlying external subroutine yourself. The CODE directive does not change the essential structure of the C call; you can modify input parameters and you can return at most one result value.
The OUTPUT: directive tells xsubpp to supply some code to package the returned result and load it back into Perl space. RETVAL is automatically declared by xsubpp to match the return value of the function. In the preceding example, the return value of sin() is the only output parameter and is listed under OUTPUT.
The CODE directive does not help if you want a variable number of input parameters or returned results. In this case, you use the PPCODE directive and explicitly manage the entire argument stack. We will have more on this in Chapter 20.
Please take a look at the XS documentation for other keywords, details, and examples.
XS supports two special procedures for automatically creating and deleting C++ objects. Consider the following XS code for a module called Car:
Car* Car::new() void Car::DESTROY() void Car::turn_left()
When you say new Car in Perl, the wrapper code corresponding to Car::new makes the C++ invocation, new Car(). Later on, when you say in Perl space, $car->turn_left(), the appropriate C++ function is automatically called. If you want to supply CODE or PPCODE directives for C++ interfaces, you can refer to the object as THIS and to the class as CLASS.
This example has one hitch. It has no clue what's in the data type Car. Unlike SWIG, which quite unconcernedly treats a Car* like a void*, xsubpp expects help in the form of a typemap. Since we need to know the internal Perl API to create a typemap, we'll leave this issue unresolved until Chapter 20.