Beefy Boxes and Bandwidth Generously Provided by pair Networks chromatic writing perl on a camel
We don't bite newbies here... much
 
PerlMonks  

Re: Perl XS

by ambrus (Abbot)
on Nov 18, 2013 at 23:52 UTC ( #1063221=note: print w/ replies, xml ) Need Help??


in reply to Perl XS

The easiest way to work with XS is to put it in a full-blown perl module built with ExtUtils::MakeMaker. I'll show you how to do that.

Meanwhile, we'll also have to fix your XS file, because the declaration of print_array_int doesn't make sense as is: XS won't know how to pass an array of ints to your print_array_int function. I've taken the liberty to rename and modify some other parts of your code too.

First, create a directory which I'll call PrintArrays, this will be the directory where your perl module exists. You'll need five files in this directory.

First, your library which does the heavy work, written in C, and a header for it. Write this to the file printfuncs.h to define the interface of your C code.

#ifndef INCLUDE__PRINTARRAYS_PRINTFUNC__H #define INCLUDE__PRINTARRAYS_PRINTFUNC__H void print_array_char(const char *array); void print_array_int(const int array[], int l); #endif

Put the implementation into printfuncs.c.

#include <stdio.h> #include <string.h> #include "printfuncs.h" void print_array_char(const char *array) { int l; int i; l = strlen(array); printf("Length of char array is %d,\n", l); for (i = 0; i < l; i++) { printf("Element array[%d] = %c,\n", i, array[i]); } } void print_array_int(const int array[], int l) { int i; printf("Length of int array is %d,\n", l); for(i = 0; i < l; i++) { printf("Element array[%d] = %d,\n", i, array[i]); } }

Now we have to write the Makefile.PL, which is the customary name for the script that calls ExtUtils::MakeMaker. Here, we'll ask MakeMaker to build your C code for you, with the same compiler as your perl is normally built. This has the advantage that your module is easy to deploy, because MakeMaker will automatically compile it together with the XS code, so you don't need an external dependency. Now if your C code were a large library used in places other than this perl module, you could of course compile it separately and require it as a separately installed dependency for this perl module, but we won't do that now. So write this to Makefile.PL

use ExtUtils::MakeMaker; WriteMakefile( NAME => 'PrintArrays', VERSION_FROM => 'PrintArrays.pm', OBJECT => 'PrintArrays$(OBJ_EXT) printfuncs$(OBJ_EXT)', ); __END__

Note the OBJECT clause which tells MakeMaker that printfuncs.c must be compiled to compile this module. This also refers to the XS part of the code which I'll show next.

By default, MakeMaker will automatically scan your module directory (recursively) for .xs and .pm files, and knows how to build and install them automatically when you build and install the module. So our XS code will reside in a file called PrintArrays.xs. Here's how it starts (the easy part).

#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "printfuncs.h" MODULE = PrintArrays PACKAGE = PrintArrays PROTOTYPES: DISABLE void print_array_char(array) const char *array

The print_array_char function will Just Work, because the XS compiler knows how to pass a perl string to a C function that expects a const char * argument. The print_array_int function is more complicated though, because it expects an array of C ints, which the XS compiler doesn't know how to create from your perl code. There are two ways around this, and I'll show the easy way first. We'll pack the integers to a C array in a wrapper function we write in perl, represented in a perl string, and pass that (plus a length) to an XS function called print_array_int2_impl. This makes the XS function quite simple. Append this to PrintArrays.xs

void print_array_int2_impl(arraypacked, len) const char *arraypacked I32 len CODE: print_array_int((const int *)arraypacked, len);

We then have to write the perl part of the code. This will do two things: load the XS extension, and provide the wrapper function called print_array_int2 that will do the above mentioned packing. The perl part of the code is in the file called PrintArrays.pm and looks like this:

package PrintArrays; use warnings; our $VERSION = "0.001"; use XSLoader; use Exporter 'import'; our @EXPORT_OK = qw(print_array_char print_array_int print_array_int2) +; XSLoader::load("PrintArrays"); sub print_array_int2 { my($arr) = @_; print_array_int2_impl(pack("i*", @$arr), 0+@$arr); } 1; __END__

The XSLoader part loads the XS extension (this module is an alternative to Dynaloader, you can use whichever you prefer). The print_array_int2 function expects a single argument: a reference to an array of integers, which it will pack and pass to the XS function, which in turn calls the C function.

Build this module by running perl Makefile.PL && make

Now, if you wanted to use this module, you would install it by make install, which would copy both PrintArrays.pm and shared the library built from the xs and C code to wherever perl modules normally reside in your perl installation. But now, we'll now just do a quick check in place. As the compiled module isn't yet installed to the normal location, but built in the ./blib directory, we'll have to tell perl to find it there. Run the following.

perl -I./blib/lib -I./blib/arch -we 'use PrintArrays qw(print_array_ch +ar print_array_int2); print_array_char("grt"); $arr = [42,52,12,46,68 +]; print_array_int2($arr);'

This should give the following output, all of which is printed directly from the C file.

Length of char array is 3, Element array[0] = g, Element array[1] = r, Element array[2] = t, Length of int array is 5, Element array[0] = 42, Element array[1] = 52, Element array[2] = 12, Element array[3] = 46, Element array[4] = 68,

I'll show a different solution for wrapping the perl_array_int C function under the fold.

Recall that as the C function expects a C array of ints, we had to pack the perl array of integers to such a structure. In the first solution, we did this from perl code. Now we'll do it from XS code, which is more complicated because we have to access a perl array directly.

Append this to PrintArrays.xs

void print_array_int(array) AV *array CODE: int *arrayc; I32 len = av_len(array) + 1; Newxz(arrayc, len, int); I32 ind; for (ind = 0; ind < len; ind++) { SV **elt = av_fetch(array, ind, 1); int val = SvIV(*elt); arrayc[ind] = val; } print_array_int(arrayc, len); Safefree(arrayc);

The new XS function called perl_array_int needs no perl wrapper, because it takes a perl array reference. (Note that I've sneakily added this XS function to the export list of PrintArrays.pm earlier, even when the XS function didn't yet exist.)

Now rebuild the module by running make, then retest with this command.

perl -I./blib/lib -I./blib/arch -we 'use PrintArrays qw(print_array_in +t); $arr = [42,52,12,46,68]; print_array_int($arr);'

This will print the following:

Length of int array is 5, Element array[0] = 42, Element array[1] = 52, Element array[2] = 12, Element array[3] = 46, Element array[4] = 68,

You can see that the two perl functions print_array_int and print_array_int2 give the same output.

For my previous XS example on perlmonks, see also Re: Opposite of strtol?.

Update: for docs on how XS works, see core perl documentation in perlxstut, perlguts, perlxs, perlapi, ExtUtils::MakeMaker.


Comment on Re: Perl XS
Select or Download Code
Re^2: Perl XS
by revendar (Novice) on Nov 19, 2013 at 09:36 UTC
    Excellent explanation. Thanks ambrus! Thanks Monks.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://1063221]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (4)
As of 2014-04-17 01:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (437 votes), past polls