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

Replies are listed 'Best First'.
Re: Pass a Perl aref to C, work on it, and get it back as a Perl array
by syphilis (Archbishop) on Jan 28, 2017 at 03:20 UTC
    I use InlineX::C2XS for most of that - though I don't think there's many others using it.
    It uses Inline::C to generate the desired XS file.
    It will also (if directed) autogenerate the manifest file, the .pm file and the Makefile.PL.

    As it stands, you're using Parse::RecDescent to parse the C code.
    If that C file becomes large you'll probably find it significantly quicker to use the ParseRegExp parser. (There's also the pegex parser, though I haven't personally tried it out.)

    Cheers,
    Rob
Re: Pass a Perl aref to C, work on it, and get it back as a Perl array
by stevieb (Canon) on Jan 27, 2017 at 17:51 UTC

    I want to clarify my reasoning behind this. In Perl, I needed to pass in an unsigned char * to a C function (hence the aref, I just haven't changed it to use pack() or the like yet).

    Then, I needed to get the return from that C function (a modified version of the unsigned char *), and return it as something that the Perl caller would be able to use (a list).

    I do not know enough about the whole process to understand whether there's a simpler or more elegant way to do what I needed to do, and based on my (first couple of passes at) reading perlxs, perlapi, perlguts and Inline::C, that's what I came up with.