Tassilo Parseval <[EMAIL PROTECTED]> writes:
>Hi all,
>
>In my XS I have two methods that basically mimic the behaviour of Perl's
>localtime() and gmtime(). To avoid some duplicate code in the XSUBs I
>thought I let them call a C function with an additional argument
>'method' (to determine whether to use gmtime(3) or localtime(3)) and
>then let this C function push the returns values directly onto the Perl
>stack. That wasn't quite that easy since it turned out that this
>involves quite some knowledge about all the stack macros. I've finally
>come up with a working solution but I am sceptical, especially since I
>don't quite understand why it works. :-)

Mostly by luck ;-) - the result returning is fine, but you are also 
messing with mark stack in ways which don't look safe.

>
>Here the C function and how it's called from one of the two XSUBs below:
>
>    int datify (FILETIME *wintime, int method) {
>        dXSARGS;

  dSP; 

should suffice here dXSARGS; does other stuff to do with geting number 
of _input_ arguments.

>        time_t time = FileTimeToUnixTime(wintime, NULL);
>        struct tm *tstruct;
>        SP -= items;

PPCODE: should have do the stack adjust for input args already.
If you need this it is most likely because extra dXSARGS has messed 
with things.

>        if (method == 0)
>            tstruct = localtime(&time);
>        else    
>            tstruct = gmtime(&time);
>        
>        PUSHMARK(SP);

PUSHMARK is for marking start of args for a perl/XS sub you are 
going to call - you should not need it for returning values.
If you found you needed it it is probably because you used dXSARGS 
which does an extra "pop" of the mark stack. Pushing a mark back 
hides that muddle but the mark you are pushing may be in wrong place.

>        if (GIMME == G_ARRAY) {
>            EXTEND(SP, 9);
>            PUSHs(sv_2mortal(newSViv(tstruct->tm_sec)));
>            [...] /* nine values overall */
>            PUSHs(sv_2mortal(newSViv(tstruct->tm_isdst)));
>            PUTBACK;
>            return 9;



>        } else {
>            SV *str = newSVpvf("%s %s %2d %02d:%02d:%02d %d",
>                      dayname[tstruct->tm_wday],
>                      [...]
>                      tstruct->tm_year + 1900);                   
>            EXTEND(SP, 1);
>            PUSHs(sv_2mortal(str));
>            PUTBACK;
>            return 1;
>        }
>    
>    [...]
>    
>    MODULE = Mail::Transport::Dbx PACKAGE = Mail::Transport::Dbx::Email
>    
>    void
>    date_local (self)
>            DBX_EMAIL *self;
>        PREINIT:
>            int count;
>        PPCODE:
>            count = datify(&(self->email->date), 0);
>            XSRETURN(count);
>
>I encounter strange behaviour if I make slight changes, for instance
>decrementing SP in date_local() instead of datify(). In this case, one
>of the return values is dropped.
>
>The only thing I know for sure so far is that I have to use dXSARGS or
>dSP (probably depending on whether I use 'items') to be able to use the
>Perl stack in a C function. But I am not at all sure about ENTER, LEAVE
>SAVETMPS and FREETMPS. So what would be a minimum sceleton to call a
>C-function from a XSUB and have the C-function set up the return stack
>accordingly? Currently I would say:
>
>    int func(...) {
>        dXARGS;
>        int num_return_values;
>        SP -= items;
>        PUSHMARK(SP);
>        /* push stuff on stack using XPUSH or EXTEND/PUSH */
>        PUTBACK;
>        return num_return_values;
>    }
>    
>    void XSfunc(...)
>        ...
>        PPCODE:
>            XSRETURN(func());

I think the minimum is as follows - I have added the THX stuff needed for 
threads/multiplicity (and which is suitably NOPed for you elsewhere):

    int func(pTHX_ ...) {
        dSP;
        int num_return_values;
        /* push stuff on stack using XPUSH or EXTEND/PUSH */
        PUTBACK;
        return num_return_values;
    }

    void XSfunc(...)
        ...
        PPCODE:
            XSRETURN(func(aTHX_ ...));

For some peoples view of simpler you could pass SP to the function 
as an SV ** and return modified value and compute number of items 
from the difference. But I think my version above is a reasonable 
trade off which still uses XS-oid macros.

>
>Is that how it is supposed to work or do other ways exist? I would like
>to know those as they might make the whole stack-thing clearer to me.

The main thing you have "wrong" is the messing with the mark stack.
That is for input args, and the implied dXSARGS; that xsubpp generates 
for you should handle all that.

The way mark stack works is that you do a PUSHMARK(sp) before you start 
pushing args for a called routine - it records where on stack 1st arg is.
It does this by pushing the address onto ANOTHER stack: the mark stack.
Then called routine does a POPMARK which pops a value off the mark stack
the difference between the value from there and the top of the main
arg stack is the number of args.

Return values over-write the args - so 1st return value goes over 1st arg 
etc. So in a CODE: style xsub using XS-ish macros args go as ST(0)..ST(N).
(So as you know you are going to return 1 or 9 values you don't _need_ 
to use PPCODE - but it is harmless.)

PPCODE: is different it does a stack adust by number of args before the 
body gets invoked - so top of stack is now pointing at 1st arg so you 
can just XPUSH() return values.

The other main thing you need to keep in mind is that EXTEND (or the 
implied one in XPUSHs) can re-allocate stack and so it may move wholesale 
in memory. I think the XS macro ST(n) dodges this issue by working in offsets
from _the_ global variable which holds bottom-of-stack. But dSP / PUTBACK 
and PUSH are working with a "cached" local copy of top-of-stack pointer.

I know the .h files are not as clear as we would like, but it is worth 
taking a look at them when you are unclear. I believe all this is 
covered in Simon's book ...


>
>Thanks in advance for any explanations,
>Tassilo
-- 
Nick Ing-Simmons
http://www.ni-s.u-net.com/

Reply via email to