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

Here is my xs routine
void GenerateSessionID(session_id) SV *session_id PREINIT: char *array = ""; STRLEN len; session_id = NULL; PPCODE: len = 17; session_id = newSVpvn(array,17); //allocating memory 16 + 1 +for null if(!SvPOK(session_id)){ XSRETURN_UNDEF; } sv_setpvn(session_id, "\0",len); //Intializing it to NULL? Don +'t know if this wrks array = SvPV(session_id,len); /*convert the scalar into a c +har*/ if(XSessionID((uuid_t*)array)){ //uuid is unsigned char[16] EXTEND(SP,17); PUSHs(sv_2mortal(newSVpv((char*)array,(int)len))); }
When I call the function in Perl as $result  = GenerateSessionID(\$uuid), I try printing result, and I get garbage. Contents of array in xsub look like [ ea 01 e8 b8 79 ed 11 e1 a7 31 00 0c 29 be c3 da ]. My question, is that how do I access this unsigned char when pushed onto the stack in perl? The memory allocated will be freed in a CloseSession call(). I would really appreciate your help. Thanks

Replies are listed 'Best First'.
Re: Need to pass an unsigned char array from an xsub back to Perl
by Eliya (Vicar) on Mar 30, 2012 at 16:30 UTC

    Your code is somewhat confusing as to whether you want to return the unsigned char[16] as a Perl array/list (16 individual values), or as a scalar (Perl string of length 16).

    Assuming the latter, I think you're approaching things in a much too complicated way.  Simply create a char[16] buffer in C, let XSessionID() fill it, and place a copy of it in an SV, which you then return:

    #!/usr/bin/perl -wl use strict; use Inline C => <<'END_C'; SV* GenerateSessionID() { char uuid[16]; // buffer // fill buffer, i.e. mimic your XSessionID() int i; for (i=0; i<16; i++) { uuid[i] = 0xA0 + i; } return newSVpvn(uuid, 16); } END_C my $uuid = GenerateSessionID(); print join ' ', unpack("(H2)*", $uuid);

    Output:

    $ ./962624.pl a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af

    Not using Inline::C (i.e. using a normal XSUB instead), this should translate to something like

    SV * GenerateSessionID() PREINIT: char uuid[16]; CODE: XSessionID((uuid_t*)uuid); RETVAL = newSVpvn(uuid, 16); OUTPUT: RETVAL

    Or, if you want to use PPCODE:

    void GenerateSessionID() PREINIT: char uuid[16]; PPCODE: XSessionID((uuid_t*)uuid); ST(0) = newSVpvn(uuid, 16); sv_2mortal(ST(0)); XSRETURN(1);
      Thank you so much for the quick reply. Indeed, your solution is very elegant. I am a newbie in xsubb. I did use the second method.
      PREINIT: unsigned char uuid_arr[16]; CODE: char convert_arr[33]; int i; XGenerateSessionID((uuid_t*) &uuid_arr); memset(convert_arr,'\0',33); for(i=0; i< 16; i++){ snprintf(&convert_arr[i*2], sizeof(convert_arr)-(i*2), "%02X" +, uuid_arr[i]); } RETVAL = newSVpvn(convert_arr,33); OUTPUT: RETVAL
      If I had not converted the unsigned char back to char, the compiler complains of signedness.
      Or, if you want to use PPCODE:
      void GenerateSessionID() PREINIT: char uuid[16]; PPCODE: XSessionID((uuid_t*)uuid); ST(0) = newSVpvn(uuid, 16); sv_2mortal(ST(0)); XSRETURN(1);
      Thats quite bloated. This is less bloated.
      void GenerateSessionID() PREINIT: char uuid[16] = {0); //zero fill PPCODE: XSessionID((uuid_t*)uuid); PUSHs(sv_2mortal((newSVpvn(uuid, 16)));
      Also the OP might want to zero his UUID since we are not checking the return value of XSessionID to determine failure.
Re: Need to pass an unsigned char array from an xsub back to Perl
by BrowserUk (Patriarch) on Mar 30, 2012 at 16:52 UTC

    You are extending the stack by 17 scalars:

    EXTEND(SP,17);</c> <p>But then pushing a single scalar <c>PUSHs( sv_2mortal( ...) );</c> <p>And that scalar is an SvPV that points to a C-string: <c>newSVpv( ( +char*)array,(int)len )</c> <p>If you want to push that string as 16 (no point in return the null +in a searate scalar) individual unisgned values, you will need to loo +p over the string extracting the byte values, creating SvUV's from ea +ch one and then push each onto the stack. <p>Something like:<code> EXTEND( SP, 16 ); for( i=0; i < 16; ++i ) PUSHs( sv_2mortal( newSVuv( array[ i ] ) ) );

    Also, there is no point in this: sv_setpvn(session_id, "\0",len); //Intializing it to NULL? Don't know if this wrks

    1) it will already have been initialised to nulls by newSVpvn(array,17); //allocating memory 16 + 1 for null

    2) if it wasn't initialised to nulls it wouldn't matter because the Xsession() sub is going to overwrite it anyway.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

    The start of some sanity?