in reply to (tye)Re: Passing References to Arrays into Perl Extensions in C
in thread Passing References to Arrays into Perl Extensions in C

Hi tye--- Thanks for your response. My reference is to a matrix of float values. Will char * still work? I don't think so.... I guess I should have made that clear in my question.
  • Comment on Re: (tye)Re: Passing References to Arrays into Perl Extensions in C

Replies are listed 'Best First'.
(tye)Re2: Passing References to Arrays into Perl Extensions in C
by tye (Sage) on Aug 03, 2001 at 19:40 UTC

    Yes, "char *" will work fine. I updated my previous node to work with floats. The "char *" tells XS that you want a pointer to the string value of the scalar that was passed in. The pack produces a string that contains a C array of floats, so the address of that can just be typecasted to get a pointer to an array of floats.

            - tye (but my friends call me "Tye")
      Sorry to belabor this point, tye--I'm not very familiar with XS, and I want to make sure I do this right. So if I have a reference to a two-dimensional array, and I just pack it and send it through to C, then can I get, inside the C code, the double pointer to the two-dimensional array of floats? I still just don't see how I work with a char * when doing the mathematical manipulations on the matrix that i need to do. Sorry again.

        Ah, this makes doing most of the work in Perl even more important (in my book, anyway). So we need to build a bunch of (packed) C arrays and a (packed) array of C pointers to these arrays. That isn't too hard. Here is some code:

        sub MyFunc { my $avMatrix= shift(@_); my $packedMatrix= ""; my @recycleBin; my $width= @{ $avMatrix->[0] }; for my $avRow ( @$avMatrix ) { $width= @$avRow if @$avRow < $width; my $packedRow= pack("f$width",@$avRow); push @recycleBin, \$packedRow; $packedMatrix .= pack("p",$packedRow); } return MyFuncC( 0+@$avMatrix, $width, $packedMatrix ); }
        and the XS code would look something like this:
        int MyFuncC( rows, columns, packedMatrix ) int rows; int columns; char * packedMatrix; CODE: RETVAL= CFunc( rows, columns, (float **)packedMatrix );

        Some explanation: A C function that takes a 2-dimensional array (matrix) usually requires that the dimensions of the matrix be passed in. So we need to figure out what to pass in for the width. To prevent the C code from trying to read off the end of the packed arrays that we'll be producing, we just set width to be the minimum width. Another reasonable choice would be to refuse to call the C code if different widths were passed in.

        The first pack creates the C (packed) array for a single row of the matrix. We need to prevent the string that contains this packed row from being garbage collected until after the C function is done, so we keep a reference to it alive in @recycleBin until the end of our Perl function.

        Then we append a pointer to the start of that packed row to our $packedMatrix, which is just a Perl string that we build up as a packed array of pointers.

                - tye (but my friends call me "Tye")

        Now, part of the "win" of writing everything that you possibly can in Perl, is that you end up with C interfaces that can be more efficient. You mentioned in chatter that your matrix is rather large. So, let's say that you want to use this C code on the same (or similar) matrix more than once. Then you might want to use something like this:

        sub PackMatrix { my $avMatrix= shift(@_); my $nRows= 0 + @$avMatrix; my $nCols= 0 + @{ $avMatrix->[0] }; my $ptrSize= length(pack"p",""); my $ptrLen= $ptrSize*$nRows; my $rowLen= length(pack"f",0)*$nCols; my $dataLen= $rowLen*$nRows; my $packedMatrix; if( pack("p",$packedMatrix) != pack("I",0+\$packedMatrix) ) { die "This platform has strange pointers!"; } length($packedMatrix)= $ptrLen + $dataLen; $packedMatrix= "\0" x $ptrLen; for my $avRow ( @$avMatrix ) { die "Row has ",0+@$avRow," entries instead of $width!" unless $width == 0+@$avRow; $packedMatrix .= pack("f$width",@$avRow); } my $ptr= $ptrLen + \$packedMatrix; for my $i ( 0..$#$avMatrix ) { substr( $packedMatrix, $i*$ptrSize, $ptrSize )= pack "I", $ptr; $ptr += $rowLen; } return \$packedMatrix; }
        Then you can pass this single string around (always by reference, never by value) and use it again and again. Sorry, I have to run now so I don't have time to explain this in detail. Feel free to ask questions. (:

                - tye (but my friends call me "Tye")