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