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

I have an XS routine that (simplified) looks like this:
foo * routine(a, b, c=NULL) char *a; int b; bar_t * c; CODE: RETVAL = croutine( a, b, (c && SvOK(ST(2)) ) ? c : cdefault ); OUTPUT: RETVAL
From perl, we can say
my $c = $objref; routine( $a, $b, $c);
or
routine( $a, $b);
BUT, if we say:
my $c; $c = $objref if( false ); routine( $a, $b, $c );
We fail with "c is not a reference" at runtime, when the interface code sees that c is not a reference.

This is because $c contains undef. But I'm prepared to supply another value in this case. (Note the SvOK(ST(2))).

How do I tell XS that a reference OR undef is OK for a parameter (c)?

Many thanks,

This communication may not represent my employer's views, if any, on the matters discussed.

Replies are listed 'Best First'.
Re: XS: How to enable passing ref or undef?
by syphilis (Archbishop) on Oct 16, 2010 at 01:05 UTC
    How do I tell XS that a reference OR undef is OK for a parameter (c)?

    I think I would just pass c to the XSub as an SV, and then let the XSub determine whether c is a reference or not ( using SvROK(c) ). Once the XSub knows what it has, you can then have it take whatever steps are needed to pass satisfactory values on to the C routine.
    If the XSub needs to know whether c is an object or not, use sv_isobject(c):
    use warnings; use Math::BigInt; use strict; use Inline C => Config => BUILD_NOISY => 1, USING => 'ParseRegExp'; use Inline C => <<'END'; int is_ref(SV * x) { if(SvROK(x)) return 1; return 0; } int is_obj(SV * x) { if(sv_isobject(x)) return 1; return 0; } END my $undef; my $ref = \$undef; my $obj = Math::BigInt->new(0); for($undef, $ref, $obj) { print is_ref($_), " ", is_obj($_), "\n"; } # Output: # 0 0 # 1 0 # 1 1
      Thanks - I appreciate the effort. But that isn't the approach I need here.

      I have made this work by making the declaration variadic, which gives me the unfettered ability to process the third argument. But it also required me to copy all the ugly casts that the xs compiler did to convert to the structure pointer.

      routine(a, b=4,...) char * a; int b PREINIT: foo_t * c = NULL; CODE: if (items > 2 ) { if (SvOK(ST(2))) { /* defined */ if (SvROK(ST(2))) { /* reference */ IV tmp = SvIV((SV*)SvRV(ST(2))); c = INT2PTR(foo_t *,tmp); } else Perl_croak(aTHX_ "c is not a reference"); } } ... continuing as before
      There must be some typemap magic that would have the same effect with less mumbling. But the documentation is sparse and the crystal is cloudy...

      This communication may not represent my employer's views, if any, on the matters discussed.

        Here is some typemap code I copied from my own project. I mostly figured this out (aka copy/pasted) from the perlxstut manpage I think.
        TYPEMAP # You need a typedef for BarPointOrNull in your .xs file or a header.. +. # Like this: typedef bar_t * BarPointerOrNull; # Then you can use it in the XS parameter list: # routine( a, b, c ) # char * a # int b # BarPointerOrNull c BarPointerOrNull T_BAR_OR_NULL INPUT #--------------------------------------------------------------------- +-------- # Map a perl reference to a C pointer... #--------------------------------------------------------------------- +-------- T_BAR_OR_NULL if ( ! SvOK( $arg )) { $var = NULL; } /* Make sure we are given an object of class Bar::Class... */ else if ( sv_derived_from( $arg, \"Bar::Class\" )) { IV tmp = SvIV( (SV *) SvRV( $arg )); $var = INT2PTR( $type, tmp ); } /* Holy crap! I must have copied this from the manpage... nice. */ else { Perl_croak(aTHX_ \"%s: %s is not of type %s\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\", \"Bar::Class\") } OUTPUT #--------------------------------------------------------------------- +-------- # Map a C pointer to a perl reference... #--------------------------------------------------------------------- +-------- T_BAR_OR_NULL if ( $var == NULL ) { $arg = &PL_sv_unef; } else { /* We bless the pointer as a Bar::Class object... can be handy * for wrapping structures in objects. */ sv_setref_pv( $arg, \"Bar::Class\", (void *)$var ); } # $var is the C variable, $type is the C type, $arg is the perl variab +le # Um that's all I know. OH and always escape your double-quotes!
        EDIT On second thought... this won't work. The XS function will complain about not enough arguments if you don't give it a third argument. You could always wrap it in a pure-perl function, though...
        # Silly really... but so easy! sub myroutine { my ($a, $b, $c) = @_; routine( $a, $b, $c ); }
Re: XS: How to enable passing ref or undef?
by andal (Hermit) on Oct 18, 2010 at 11:09 UTC

    From your example it is not clear why are you using ST(2) in CODE section. In general, the declaration of function arguments tell xsubpp how to map perl variables into C variables. If you look at the generated by call "perl Makefile.PL" .c file, then you'll see how exactly the moving from perl variables to C variables shall happen.

    You can affect this transition by providing initialization to your variables in the declaration. Then you don't need to provide CODE section. See for more details in perldoc perlxs.

    One way to handle your situation could also be this. I don't know if this is what you want

    foo * routine(a, b, c=NULL) SV * a SV * b SV * c INIT: ... if(!SvROK(c)) croak("Expected reference"); CODE: { SV * str = SvRV(c); }

    Note. In your example you are using SvOK, I guess you want to use SvROK if you want to catch the reference.