arraysperlxs

perl xs - return perl array from c array


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?


Solution

  • 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:

    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:

    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:

    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.