9

Using XS i am trying to pass values from a C array into a Perl array that can be used in the script.

Here is the code from my xs file:

AV *
DoubleArray::getPerlArray()
    CODE:
    r = newAV();
    for(size_t i=0; i < THIS->count; i++)
    {
        av_push(RETVAL,newSVnv(THIS->data[i]));
    }
    OUTPUT:
    RETVAL

It compiles fine but when I run the following in perl:

my @d = $C->getPerlArray();
foreach(@d)
{
    print "$_\n";
}

It just prints ARRAY(0x1408cdc) when I am expecting it to print a list of numbers.

How can I modify my code to correctly pass back a perl array?

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • https://www.nntp.perl.org/group/perl.xs/2011/06/msg2626.html seems relevant – simbabque Oct 12 '17 at 21:36
  • @simbabque I've seen that example before. It's kind of a mess. Hard to learn from. –  Oct 12 '17 at 23:06
  • 1
    Would you mind sharing some of the things you have already learned with the community? You could maybe add a chapter or two to https://github.com/xsawyerx/xs-fun. :-) – simbabque Oct 13 '17 at 07:26

1 Answers1

12

Perl subs can only return (0 or more) scalars. When you tried to return an array (impossible without crashing Perl!), the default typemap returned a reference to that array instead.

Note that your program also leaks memory (because the default typemap for AV* should mortalize your array but doesn't).


Returning a reference, method 1

AV* /* Returns: sv_2mortal(newRV(RETVAL)) */
DoubleArray::getPerlArrayRef()
    PREINIT:
        size_t i;
    CODE:
        RETVAL = (AV*)sv_2mortal((SV*)newAV());
        for (i=0; i < THIS->count; ++i) {
            av_push(RETVAL, newSVnv(THIS->data[i]));
        }

    OUTPUT:
       RETVAL

Memory leak check:

  • Array's refcnt: 1 (newAV) -1[delayed] (sv_2mortal) +1 (newRV) = 1[delayed] (owned by reference)
  • Reference's refcnt: 1 (newRV) -1[delayed] (sv_2mortal) = 0[delayed]

Perl:

my $array = $C->getPerlArrayRef();
say for @$array;

Returning a reference, method 2

SV* /* Returns: sv_2mortal(RETVAL) */
DoubleArray::getPerlArrayRef()
    PREINIT:
        AV* av;
        size_t i;
    CODE:
        av = newAV();
        RETVAL = newRV_noinc((SV*)av);
        for (i=0; i < THIS->count; ++i) {
            av_push(av, newSVnv(THIS->data[i]));
        }

    OUTPUT:
       RETVAL

Memory leak check:

  • Array's refcnt: 1 (newAV) +0 (newRV_noinc) = 1 (owned by reference)
  • Reference's refcnt: 1 (newRV_noinc) -1[delayed] (sv_2mortal) = 0[delayed]

Perl: <same as above>


Returning a reference, method 3

void
DoubleArray::getPerlArrayRef()
    PREINIT:
        AV* av;
        size_t i;
    PPCODE:
        av = newAV();
        mXPUSHs(newRV_noinc((SV*)av));
        for (i=0; i < THIS->count; ++i) {
            av_push(av, newSVnv(THIS->data[i]));
        }

Memory leak check:

  • Array's refcnt: 1 (newAV) +0 (newRV_noinc) = 1 (owned by reference)
  • Reference's refcnt: 1 (newRV_noinc) -1[delayed] (mXPUSHs) = 0[delayed]

Perl: <same as above>


Returning scalars

We have to check context because we can't place more than one scalar on the stack outside of list context.

void
DoubleArray::getElements()
    PREINIT:
        size_t i;
        U8 gimme = GIMME_V;
    PPCODE:
        if (gimme == G_ARRAY) {
            EXTEND(SP, THIS->count);
            for (i=0; i < THIS->count; ++i) {
                mPUSHn(THIS->data[i]);
            }
        }
        else if (gimme == G_SCALAR) {
            mXPUSHu(THIS->count);
        }

Perl:

my $count = $C->getElements();
say $count;

my @array = $C->getElements();
say for @array;

Note: The refcnt decrement by sv_2mortal is delayed until the caller has a chance to increment the refcnt.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 2
    Excellent answer! Thanks! –  Oct 12 '17 at 23:02
  • 1
    Great answer! Note for Perl version >= 5.16, you can also declare the typemap `AV* T_AVREF_REFCOUNT_FIXED` if you do not care about backwards compatibility, see the section *"Returning SVs, AVs and HVs through RETVAL"* in [`perlxs`](https://perldoc.perl.org/perlxs.html#Returning-SVs%2c-AVs-and-HVs-through-RETVAL) for more information. – Håkon Hægland Oct 13 '17 at 06:47
  • Yeah, figured the answer was already long enough without introducing that. – ikegami Oct 13 '17 at 14:49