...and how to use the generated XS code in your own module.
This is a tutorial as much as it is a request for guidance from experienced XS/C/perlguts folks, as TIMTOWTDI, and in this case, likely, a better way (like working on the array reference directly, which I've yet to figure out how).
This will show you how to pass a Perl array reference (aref) into a C function, convert the aref into a C array, work on it, then push it back onto the stack so the C function returns it as a Perl array (actually a list, but I digress).
It'll also show that although we bite off of Inline::C, the XS code it generates can be used in your distribution, even without the end-user needing Inline installed.
First, straight to the code. Comments inline for what's happening (or, at least, what I think is happening... feedback welcomed):
use warnings; use strict; use feature 'say'; use Inline 'Noclean'; use Inline 'C'; my $aref = [qw(1 2 3 4 5)]; # overwrite the existing aref to minimize memory # usage. Create a new array if you need the existing # one intact @$aref = aref_to_array($aref); say $_ for @$aref; __END__ __C__ void aref_to_array(SV* aref){ // check if the param is an array reference... // die() if not if (! SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV){ croak("not an aref\n"); } // convert the array reference into a Perl array AV* chars = (AV*)SvRV(aref); // allocate for a C array, with the same number of // elements the Perl array has unsigned char buf[av_len(chars)+1]; // convert the Perl array to a C array int i; for (i=0; i<sizeof(buf); i++){ SV** elem = av_fetch(chars, i, 0); buf[i] = (unsigned char)SvNV(*elem); } // prepare the stack inline_stack_vars; inline_stack_reset; int x; for (x=0; x<sizeof(buf); x++){ // extract elem, do stuff with it, // then push to stack char* elem = buf[x]; elem++; // the sv_2mortal() rectifies refcount issues, // and ensures there's no memory leak inline_stack_push(sv_2mortal(newSViv(elem))); } // done! inline_stack_done; }
We now get an _Inline directory created within the current working directory, which has a build/ dir and then a sub directory (or multiple, just look at the one with the most recent timestamp). Peek in there, and you'll see a file with an .xs extention. This is the file you want if you want to include your work into a real Perl distribution. This essentially allows one to utilize my favourite feature of Inline::C, which is to build XS code for us, without having to know any XS (or little XS) at all.
After I run the above example, I get this in the XS file (my comments removed):
#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "INLINE.h" void aref_to_array(SV* aref){ if (! SvROK(aref) || SvTYPE(SvRV(aref)) != SVt_PVAV){ croak("not an aref\n"); } AV* chars = (AV*)SvRV(aref); unsigned char buf[av_len(chars)+1]; int i; for (i=0; i<sizeof(buf); i++){ SV** elem = av_fetch(chars, i, 0); buf[i] = (unsigned char)SvNV(*elem); } inline_stack_vars; inline_stack_reset; int x; for (x=0; x<sizeof(buf); x++){ char* elem = buf[x]; elem++; inline_stack_push(sv_2mortal(newSViv(elem))); } inline_stack_done; } MODULE = c_and_back_pl_f8ff PACKAGE = main PROTOTYPES: DISABLE void aref_to_array (aref) SV * aref PREINIT: I32* temp; PPCODE: temp = PL_markstack_ptr++; aref_to_array(aref); if (PL_markstack_ptr != temp) { /* truly void, because dXSARGS not invoked */ PL_markstack_ptr = temp; XSRETURN_EMPTY; /* return empty stack */ } /* must have used dXSARGS; list context implied */ return; /* assume stack size is correct */
To note is the following line:
MODULE = c_and_back_pl_f8ff PACKAGE = main
That dictates the name of the module you're creating the XS for. You'll want to change it to something like:
MODULE = My::Module PACKAGE = My::Module
...then put that file in the root of your distribution, and add, into your distribution's primary .pm module file:
require XSLoader; XSLoader::load('My::Module', $VERSION);
Normally, the #include INLINE.h can be removed, but because we're using some Inline functionality, we need to grab a copy of INLINE.h from somewhere and copy it into the root directory of our distribution so that everything compiles nicely. There's always a copy of it in the _Inline/build/* directory mentioned above. Providing this header file will allow users of your distribution that don't have Inline::C installed to use your module as if they did have it.
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |