3

I am new to Perl XS and I am trying to convert a C function to Perl subroutine.

I have the following C function

void parse(struct parser *result, const char *string, size_t len);

where the parse function accepts a pointer to struct parser, a string and the length of the string. struct parser is defined something like this:

struct parser {
    char *data;
    long  a;
    long  b;
    long  c;
};

The function stores its results in the result argument.

I want to convert this function to Perl XS. What I am doing is something like this:

struct parser *result
parse_xs (string)
    const char* string
PREINIT:
    long len = strlen(string);
CODE:
    struct parser par;
    parse(&par,s,len);
    RETVAL = par;
OUTPUT:
    RETVAL  

How can I change the above code to run parse_xs in Perl code like this

my $result = parse_xs();

print $result->data; # will print the data field from the struct.

where $result is the result of the parse C function.

nwellnhof
  • 32,319
  • 7
  • 89
  • 113
smith
  • 3,232
  • 26
  • 55
  • You'll need to create an XS func. What's `data`'s type? – ikegami Aug 05 '14 at 21:07
  • its char * i added the struct fields to the questions to make it more clear. – smith Aug 05 '14 at 21:20
  • You may be interested in [Inline::Struct](https://metacpan.org/pod/distribution/Inline-Struct/Struct.pod). Even if you don't want to use Inline in your code (because the entire premise of Inline is pretty crazy), it might be useful in development - you can probably intercept the XS code that it generates and use that as a crib sheet. – tobyink Aug 06 '14 at 09:24

2 Answers2

6

First, you have to choose a name for your result class. I'll simply use ParseResult from now on.

You'll also need a typemap file. To map C structs to Perl classes, use the built-in T_PTROBJ:

TYPEMAP
ParseResult     T_PTROBJ

Then add a typedef and the ParseResult package to your XS code:

typedef struct parser *ParseResult;

MODULE = YourModule  PACKAGE = ParseResult

Make sure that the typedef comes before all MODULE sections. Now you can add the XSUB for the parse_xs function (you should probably just name it parse):

ParseResult
parse_xs(string)
    SV *string
PREINIT:
    const char *c_string;
    STRLEN len;
CODE:
    Newx(RETVAL, 1, struct parser);
    c_string = SvPV(string, len);
    parse(RETVAL, c_string, len);
OUTPUT:
    RETVAL

Note that I allocate memory for the result struct using Perl's Newx function. Returning a pointer to a struct on the stack cannot work. I also added some minor optimizations like passing the string as SV and getting the content and length using SvPV.

In order to free the allocated memory, you have to implement a destructor (named DESTROY in Perl):

void
DESTROY(result)
    ParseResult result
CODE:
    /* Possibly free data in result here. */
    Safefree(result);

Your result struct probably points to other allocated memory. Make sure to free this memory before calling Safefree.

Then you can write accessors like this:

const char *
data(result)
    ParseResult result
CODE:
    RETVAL = result->data;
OUTPUT:
    RETVAL

Now you have to write a Perl module for your class. Add a file named ParseResult.pm with the following contents:

package ParseResult;
use strict;
use warnings;

use XSLoader;

our $VERSION = '0.01';
XSLoader::load('ParseResult', $VERSION);

1;

Your new XS-based class can then be used like this:

use ParseResult;

my $result = ParseResult::parse_xs('input');
print $result->data, "\n";

Note that I use a fully-qualified name for parse_xs. If you want to call it without the class name, you have to export it from ParseResult.pm.

nwellnhof
  • 32,319
  • 7
  • 89
  • 113
2

You can use C struct as Perl Object. See the following page(This page is Japanese, but you may understand source code).

http://d.hatena.ne.jp/perlcodesample/20140807/1407291461

// creat struct as pointer
Point* point = (Point*)malloc(sizeof(Point));
point->x = x;
point->y = y;


// Convert pointer to size_t
size_t point_iv = PTR2IV(point);

// Convert size_t to SV*
SV* point_sv = sv_2mortal(newSViv(point_iv));

// Create reference to SV*
SV* point_svrv = sv_2mortal(newRV_inc(point_sv));

// Create Object
SV* point_obj = sv_bless(point_svrv, gv_stashpv(class_name, 1));