Send in a Perl aref to C, get back a Perl array (and using the generated XS)
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.
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.
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
@$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++;
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
distributions primary .pm
module file:
require XSLoader;
XSLoader::load('My::Module', $VERSION);
Normally, the INLINE.h
include
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.