perl xs - return perl array from c array
Asked Answered
M

1

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?

Minier answered 12/10, 2017 at 21:19 Comment(3)
nntp.perl.org/group/perl.xs/2011/06/msg2626.html seems relevantTabithatablature
@Tabithatablature I've seen that example before. It's kind of a mess. Hard to learn from.Minier
Would you mind sharing some of the things you have already learned with the community? You could maybe add a chapter or two to github.com/xsawyerx/xs-fun. :-)Tabithatablature
M
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.

Mordacious answered 12/10, 2017 at 21:48 Comment(3)
Excellent answer! Thanks!Minier
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 for more information.Silvestro
Yeah, figured the answer was already long enough without introducing that.Mordacious

© 2022 - 2024 — McMap. All rights reserved.