Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl Inline C: Passing Arrayref to C Function

I can't get arrayrefs passed into a C function using Inline C. I would like some help, please.

First, just to prove I can get Inline C to work, I'll pass a scalar value to a C function:

#!/usr/bin/perl -I. 
#
# try1.pl
#
use Inline C;

my $c = 3.8;
foo( $c );

__END__
__C__
void foo( double c )
{
   printf( "C = %f\n", c );
}

And run it:

% ./try1.pl
C = 3.800000

Now do the same thing, but with an arrayref:

#!/usr/bin/perl -I. 
# 
# try2.pl
#
use Inline C;

my @abc = (1.9, 2.3, 3.8);
foo( \@abc );

__END__
__C__
void foo( double *abc )
{
    printf( "C = %f\n", abc[2] );
}

Run it:

% ./try2.pl
Undefined subroutine &main::foo called at ./try1.pl line 7.

Any ideas what I'm doing wrong? Help greatly appreciated!

like image 435
AndyFroncioni Avatar asked Aug 17 '13 18:08

AndyFroncioni


1 Answers

Inline::C is smart enough to extract values from SV's based on your C function's type signature. But if you want to pass complex Perl structures to C functions you'll need to use the Perl API to extract the values. So, here's what you need to know for this problem:

An array is an instance of a C struct called AV. A reference is implemented by a struct called an RV. All of these are "subtypes" (kinda) of a base struct called SV.

So to make this function work we need to do a few things.

  1. Change the parameter type to SV * (pointer to an SV).
  2. Use the API to check if this particular SV is a reference as opposed to some other kind of scalar
  3. Check the RV to make sure it's pointing at an array and not something else.
  4. Dereference the RV to get the SV that it points to.
  5. Since we know that SV is an array, cast it to AV and start working with it.
  6. Lookup the third element of that array, which is another SV.
  7. Check that the SV we got from the array is a numerical value suitable for C printf
  8. Extract the actual numerical out of the SV.
  9. Print the message

So putting that all together, we get something like this:

use Inline C;

my @abc = (1.9, 2.3, 3.8);
foo( \@abc );

__END__
__C__
void foo( SV *abc )
{
    AV *array;     /* this will hold our actual array */
    SV **value;    /* this will hold the value we extract, note that it is a double pointer */
    double num;    /* the actual underlying number in the SV */

    if ( !SvROK( abc ) ) croak( "param is not a reference" );
    if ( SvTYPE( SvRV( abc ) ) != SVt_PVAV ) croak( "param is not an array reference" );

    /* if we got this far, then we have an array ref */
    /* now dereference it to get the AV */
    array = (AV *)SvRV( abc );

    /* look up the 3rd element, which is yet another SV */
    value = av_fetch( array, 2, 0 );

    if ( value == NULL ) croak( "Failed array lookup" );
    if ( !SvNOK( *value ) ) croak( "Array element is not a number" );

    /* extract the actual number from the SV */
    num = SvNV( *value );

    printf( "C = %f\n", num );
}

Kinda makes you appreciate how much work Perl does under-the-hood. :)

Now, you don't have to be as super-explicit as that example. You could get rid of some of the temp variables by doing things inline, e.g.

printf( "C = %f\n", SvNV( *value ) );

would eliminate the need to declare num. But I wanted to make it clear how much dereferencing and type-checking is needed to traverse a Perl structure in C.

And as @mob points out below, you don't actually have to do all that work (though it's a good idea to be familiar with how it works.)

Inline::C is smart enough that if you declare your function as

void foo( AV *abc ) { 
   ...
}

It will automatically unwrap the AV for you and you can go straight to the av_fetch step.

If all of that seems baffling to you, I highly recommend taking a look at:

  1. The Perlguts Illustrated PDF, then
  2. The perlguts manpage, and then
  3. The Inline::C Cookbook, while consulting
  4. The perlapi manpage.
like image 172
friedo Avatar answered Sep 18 '22 01:09

friedo