http://www.perlmonks.org?node_id=880059

unlinker has asked for the wisdom of the Perl Monks concerning the following question:

Thanks to the help from the wise monks here, (see A few very basic questions about Extending Perl), I have made slow but steady progress in writing XS routines. As mentioned there, I want to integrate a C Library (whose shared object is available but not the source) to manage sessions. Everything works as I want, except for a lurking suspicion that being a noob, I have left out something important :)

Here is the standalone XSUB that essentially mimics the C Library that I want to integrate. I have implemented only 3 of the important functions here — a function that allocates some space and sends me a handle to that space (perl sees the pointer to a struct cast to integer); a function that gets the variables of that struct; and finally a function that frees the allocated space when my session is over. I need to pass the handle (an int in perl) to access each function in the Library. Of course this is only a sample implementation of what the C-library provides with all the other parameters removed:

#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" typedef struct { int id; int desc; char* str; } session; MODULE = MySession PACKAGE = MySession void new(str) char* str PREINIT: session* tmppt; int tmpint; PPCODE: tmppt = (session*) malloc(sizeof(session)); tmppt->id = 42; tmppt->desc = 4242; tmppt->str = str; tmpint = (int) tmppt; EXTEND(SP, 1); PUSHs(sv_2mortal(newSViv((int) tmppt))); MODULE = MySession PACKAGE = MySession void get(hndl) int hndl PREINIT: session* tmppt; PPCODE: tmppt = (session *)hndl; EXTEND(SP, 3); PUSHs(sv_2mortal(newSViv(tmppt->id))); PUSHs(sv_2mortal(newSViv(tmppt->desc))); PUSHs(sv_2mortal(newSVpv(tmppt->str, 0))); MODULE = MySession PACKAGE = MySession void disconnect(hndl) int hndl CODE: free((session*) hndl);

And here is a test file for the routines:

#!/usr/bin/env perl use strict; use warnings; use ExtUtils::testlib; use MySession; my $x = MySession::new("Session ID Goes Here"); print "$x\n"; my ($p, $q, $str) = MySession::get($x); print "$p\n$q\n$str\n"; my $y = MySession::new("Another Session ID Goes Here"); print "$y\n"; ($p, $q, $str) = MySession::get($y); print "$p\n$q\n$str\n"; my $z = MySession::new("Yet Another Session ID Goes Here"); print "$z\n"; ($p, $q, $str) = MySession::get($z); print "$p\n$q\n$str\n"; MySession::disconnect($x); MySession::disconnect($y); MySession::disconnect($z); __END__

Now for the questions:

  1. The session struct contains a pointer to a string. This string is passed from the Perl code. I am assuming that perl will free string so I do not specifically free the memory associated with the string. I just free the memory associated with the struct as a whole. Is this correct?
  2. I want to now use the functions implemented here as methods in a Moose class. I can always define a module (as I have done here) say MySession and "use" this class in my Moose class and thereby access those functions. But surely there must be a better way? Can you help me with integrating this XSUB concisely with my Moose class or direct me to documentation on how this can be achieved?
  3. Finally, any pointers on improving the XSUB itself (as implemented here) and the test script would be great.

Thank you once again for your patience and your wisdom

Replies are listed 'Best First'.
Re: More Questions about Extending Perl
by Anonyrnous Monk (Hermit) on Jan 02, 2011 at 13:28 UTC
    1. The session struct contains a pointer to a string. This string is passed from the Perl code. I am assuming that perl will free string so I do not specifically free the memory associated with the string.

    This is right, basically, but it's still risky business.  As you have it, the string your C struct points to is the PV part1 of a scalar variable, which might have gone out of scope (and thus the PV being freed for reuse, too) at the time some of your other routines is referencing it.  For example, consider the following case:

    sub get_connection { # ... my $sess_id = "Session ID Goes Here"; return MySession::new($sess_id); } # $sess_id out of scope now my $x = get_connection(); my ($p, $q, $str) = MySession::get($x); # internally references PV p +art of $sess_id

    This might not lead to problems right away, because Perl's memory allocation often doesn't reuse the memory immediately, but there's no guarantee whatsoever that the respective memory location will continue to hold the same string content after the associated SV has gone out of scope.

    To not run into potential problems, you'd have to make sure one of the following

    • the respective scalar ($sess_id here) doesn't go out of scope, either by
      • writing the Perl code such that it doesn't happen (easy to overlook, once you've forgotten the XS implementation details!), or
      • incrementing the SV's reference count on the XS side (and decrementing it when you no longer need it)
    • copy the string into newly allocated memory on the XS side, which you then free yourself

    ___

    1 to verify, dump $sess_id on the Perl side using Devel::Peek, and print the address of str on the C side (printf("addr of str: %p\n", str);).  You'll get something like

    SV = PV(0x750b78) at 0x777ce8 REFCNT = 1 FLAGS = (PADMY,POK,pPOK) PV = 0x7723c0 "Session ID Goes Here"\0 CUR = 20 LEN = 24 addr of str: 0x7723c0

    Note the same address 0x7723c0.

      Thanks! That was very clearly explained. I would go with copying that string into XS space and free it myself. Any pointers on the Moose bit?

        I don't think you need those pointers. There's always more than one way to do it :) You can implement all of the functions in XS using the CODE section, or you can implement just few basic ones, and provide the rest of the code in your .pm file. Whatever you find most suitable.

        Couple of things you may find useful. The PACKAGE can be used to place the function into different name space. For example

        MODULE=MySession PACKAGE=MySession::submodule void my_func()
        With this you'll have MySession::submodule::my_func. Also, you don't need to put this declaration before each function. Only where you want to switch the current package/module.

        You can bless your scalar into object and then call other functions as methods of the object. Like this

        SV * new(pkg) SV * pkg INIT: if(!SvPOKp(pkg) || SvCUR(pkg) == 0) croak("Expected package name as argument"); CODE: { struct my_struct * wrap; SV * obj; HV * stash; Newx(wrap, 1, struct my_struct); # init the wrapper # create perl variable that holds pointer to my structure # I may need it for the cases when I have to call a perl # function and provide object to it. wrap->holder = newSVuv(PTR2IV(wrap)); # get the namespace for blessing stash = gv_stashsv(pkg, 0); if(stash == NULL) croak("No stash for package %s", SvPV_nolen(pkg)); # this is the reference that I shall return obj = newRV_noinc(wrap->holder); # Now I bless the object, really the holder of my # structure is blessed, not the reference to it! RETVAL = sv_bless(obj, stash); } OUTPUT: RETVAL
        But you have to make sure that you declare method DESTROY in your package. This method shall be called when the last reference to the holder (not to the returned reference!) is gone. At this point you shall release the memory you have allocated. Of course, you may request that the user calls special function that releases the object, but this is error prone and not really needed.
        void DESTROY(obj) SV * obj INIT: struct my_struct * wrap; SV * holder; IV val; void * wrap; if(!SvROK(obj)) croak("Expected reference to object"); holder = SvRV(obj); if(SvTYPE(holder) != SVt_PVMG) croak("The object type is wrong"); val = SvIV(holder); wrap = INT2PTR(struct my_struct *, val); CODE: Safefree(wrap);