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

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

Hi,
In response to XS on 64-bit: Warning from sv_vsetpvf call almut posted some code that led me to the following Inline::C script:
use warnings; use Inline C => <<'EOC'; void vatest__(char* format, va_list* args) { char buf[1024]; vsprintf(buf, format, *args); errno = 0; // "Success" perror(buf); } void vatest_(char* format, va_list* args) { vatest__(format, args); } void vatest(char* format, ...) { va_list args; va_start (args, format); vatest_(format, &args); va_end (args); } SV * foo () { char* msg0 = "vatest"; char* msg1 = "foo"; char* msg2 = "bar"; double d = 12.345; unsigned long l = 1234567890; vatest("%s %s", msg0, msg1); vatest("%s %s %s %u %f", msg0, msg1, msg2, l, d); return newSViv(0); } EOC foo();
which outputs (on Win32):
vatest foo: No error vatest foo bar 1234567890 12.345000: No error
which is correct and as expected.

But then I got to wondering whether vatest() could be called directly from perl - ie, instead of having the perl section of the above script do foo(); have it do something like vatest('%s %s', 'foo', 'bar'); which would output foo bar: No error.
But, of course, that doesn't work - and I'm unable to find a way of making it work.

So, is there a way of accessing the XSub vatest() directly from perl (with either XS or Inline::C) ? I'm pretty sure there isn't ... but I'm so often wrong. Faik, there could well be some clever typemapping (or other technique) that makes it possible.

Cheers,
Rob

Replies are listed 'Best First'.
Re: XS/Inline::C and ellipsis syntax
by renodino (Curate) on Jun 28, 2008 at 14:31 UTC
    Note: this is XS only; I don't know if Inline supports varargs method signatures

    See "Variable-length Parameter Lists" in perlxs. You'll have to change the vatest method signature (but you'd have to do that anyway to make it XS compatible). Which may mean that foo() has to change the way it calls vatest.


    Perl Contrarian & SQL fanboy
      You'll have to change the vatest method signature

      I'm mainly interested in keeping the vatest() signature as is - though, admittedly, that probably wasn't very clear from my post.

      I know how to work things with vatest(SV* format, ...), but was wondering if there's any way that perl can access vatest(char* format, ...) even if we impose limitations that, eg, all of the arguments passed to vatest are strings (PV's).

      It's probably a dumb question. I know that in both XS and Inline::C perl, can access foo(char* str) and can also access the multi-arg form foo(char* str1, char* str2, char* str3), but to access a variable-length list of char*'s might stretch the friendship between perl and C a little too far.

      Cheers,
      Rob

        The number of arguments to a varags C function is known at compile time. The number of arguments to a Perl function isn't known until runtime. That is, when you code:

        XS_func( char *templ, ... ) { ... vsprintf( templ, x, y, z ); }

        The C compiler knows it is passing 4 parameters to the varargs function printf and stacks them appropriately. But when you call the XS function from Perl, you can pass any number of arguments at runtime, so there is no way to write that call to vsprintf() with an appropriate number of arguments.

        About the best you could do is code the call to vsprintf() with an arbitrarily large number of arguments and yell if the Perl code passes you more:

        #! perl -slw use strict; #use Inline 'FORCE'; use Inline C => 'DATA', NAME => 'varargs', CLEAN_AFTER_BUILD => 0; myPrintf( '%s ' x @ARGV . "\n", @ARGV ); __DATA__ __C__ #include <windows.h> #include <stdio.h> #include <stdarg.h> void eprintf( const char *template, ... ) { va_list ap; va_start( ap, template ); vfprintf( win32_stderr(), template, ap ); va_end( ap ); } void myPrintf( char *templ, ... ) { inline_stack_vars; char* a[20]; int i; if( inline_stack_items > 20 ) croak( "myPrintf can handle at most 20 arguments." ); for( i = 0; i < inline_stack_items; i++ ) { a[ i ] = SvPVX( inline_stack_item( i ) ); } eprintf( a[ 0], a[ 1], a[ 2], a[ 3], a[ 4], a[ 5], a[ 6], a[ 7], a[ 8], + a[ 9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], a[17], a[18], + a[19] ); inline_stack_reset; }

        That's crude. it should be checking that the SVs have a PV assigned and lots of other stuff, but it will pass whatever args (upto 20) you give on the command line and print them to stderr via C and eprintf().

        It could also inspect the contents of the template and attempt to extract the relevant IV/NV/PV, but then you'd have to do something trick with the declaration of a[].

        Another way to do this would be for the XS/Inline C function to push the (relevant parts of) the SVs from the Perl stack, onto the C stack. That is, it would have to emulate the C compilers prologue and epilogue code manually, before transferring control (assembler call instruction) to the varargs C function.

        (And it would have to be the relevant prologue code depending upon the calling convention (__stdcall, __cdecl or __fastcall) declared for that function.)

        This approach would require compiler dependant asm{} hacks.


        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.
        Hmmm. I believe I now understand your dilemna.

        Basically, you want to be able to dynamically create parameter lists at runtime in C the same as Perl.

        Alas, it can't be done. Well, at least not easily, and definitely not in a portable manner.

        Long ago, when Kernigan and Ritchie still had full heads of hair with nary a gray follicle, some C compilers supported the varargs' complementary ability to create variable length argument lists. (I vaguely recall using it occasionally on SysVR3 and MSDOS...aha, look what google found!).

        But somewhere in the past 2 decades, our friends on the various C standards committees seem to have mislaid that capability. Presumably because it was very dangerous, and because the way stackframes get built on different platforms varies significantly.

        So about the only way to do that sort of thing these days is to dive into the assembler and handcraft your own stackframes. Or, more likely, rewrite your programs to avoid the need for dynamically building argument lists. Which unfortunately usually means writing a lot more code.


        Perl Contrarian & SQL fanboy
Re: XS/Inline::C and ellipsis syntax
by salva (Canon) on Jun 29, 2008 at 16:31 UTC
      I can see that the replies received pretty much confirm what I suspected. (I certainly don't want to go delving into assembler hacks.)

      Not much more to be said.
      Thanks renodino, BrowserUk, salva.

      Cheers,
      Rob
      Update: Gee ... this post can be read as being somewhat dismissive ... not what I intended.
Re: XS/Inline::C and ellipsis syntax
by dzaebst (Initiate) on Jun 30, 2008 at 15:02 UTC
    I'll agree that it introduces too many problems and should be avoided, especially if the code needs to be portable. However, if absolutely necessary, there's the option to create a stack, then pass the pointer off to a va_arg() call. The stack can be made by testing SvNOK, SvPOK, or SvIOK and incrementing the pointer accordingly. Some compilers want a pointer that increments, others want pointers that de-increment. On retrieval, the format string indicates how the va_arg should be taken off the stack (int, char, double).

    This example is the clearest thing I've found on forged va_lists. It also explains how the alignment can cause problems:
    archLinux