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

I am currently writing my first XS-module (just a wrapper around the C math-library) with ok-ish success. The biggest issue is the documentation that is quite hard to understand and/or is incomplete.

I have successfully written a constructor in XS and implemented some functions out of the library as method calls. That works fine.

Now I want to implement a procedural interface, too. For this reason I need to know if its a method call or not. If its a method call the number to compute with the function is stored in the instance, if its a procedural call to a function its a number given as the first argument. This is the current code for the cosine-function:

double cos(...) CODE: SV *arg = newSVsv(ST(0)); if (sv_isobject(arg)) { HV *self_hv = MUTABLE_HV(SvRV(arg)); SV **callback_ptr = hv_fetchs(self_hv, "Number", 0); SV *zahl = *callback_ptr; } else { SV *zahl = newSVsv(arg); } double x = SvNV(zahl); RETVAL = cos(x); OUTPUT: RETVAL
Thanks in advance!

Replies are listed 'Best First'.
Re: Procedural and object oriented interface in Perl/XS (minimize XS)
by tye (Sage) on Oct 23, 2014 at 13:47 UTC

    Don't write Perl code in XS. Write in XS only the parts that wrap the C library. And write the XS wrapper using a C-friendly interface (you might even want to use Inline::C to write the wrapper to encourage not trying to write Perl code in XS). Then make a nice Perl-like interface over that by writing that interface in Perl.

    This will reduce how buggy your code is (XS code is so easy to write bugs in), will surely give you a more robust and Perl-friendly interface, will make it easier to maintain, enhance, and debug your code.

    And even with this approach, I would avoid writing subs that try to act as both a method and as a non-method. If you want to support exporting non-method subroutines, then just have exportable tiny wrappers that call the methods (or, if there is absolutely no object data, then you can make the methods be tiny wrappers around the non-methods).

    double cos( double x ) /* No need to write the trivial XS wrapper, just the declaration */
    package Math::MyWrapper; use Math::MyWrapper::XS(); use Exporter 'import'; our @EXPORT_OK = qw< cos ... >; sub new { require Math::MyWrapper::Class; return "Math::MyWrapper::Class"; } sub cos { my( $x ) = @_; return Math::MyWrapper::XS::cos( $x ); } ...
    package Math::MyWrapper::Class; use Math::MyWrapper(); for my $sub ( @Math::MyWrapper::EXPORT_OK ) { eval "sub $sub { Math::MyWrapper::$sub( \@_[1..\$#_] ) }; 1" or die "Error building $sub method: $@\n"; } 1

    - tye        

Re: Procedural and object oriented interface in Perl/XS
by dave_the_m (Monsignor) on Oct 23, 2014 at 12:23 UTC
    Perl doesn't make any distinction between a function call and a method call (except that which function to call is resolved at run-time). $x->foo($y,$z) just calls foo($x,$y,$z),and Class->foo($y,$z) just calls foo('Class',$y,$z).

    So if you want your function (be it perl or XS code) to tell the difference, you need to make the function be called in a slightly different way to make a distinction

    Dave.

      Well the difference is: the method call gives the instance of the object as the only argument, the function call a number (For cos: $instance->cos; or MyModule::Math::cos(42);) and thats what I try to do with sv_isobject(). But this dows not work and I don't see my error.

        But this dows not work
        Can you be much more specific please. In which way does it not work? Under what circumstances does it not work? Have you single-stepped that XS function under a debuggger? At what point does the behaviour deviate from what you expect? Etc.

        Dave.

Re: Procedural and object oriented interface in Perl/XS
by syphilis (Archbishop) on Oct 24, 2014 at 04:30 UTC
    Here's an Inline::C demo that does pretty much the same thing, but bypasses the creation of a couple of extra SV's:
    use strict; use warnings; use Inline C => Config => CLEAN_AFTER_BUILD => 0, BUILD_NOISY => 1; use Inline C => <<'EOC'; double wrap_cos(SV * in) { if(sv_isobject(in)) { HV *self_hv = MUTABLE_HV(SvRV(in)); SV **callback_ptr = hv_fetchs(self_hv, "Number", 0); return cos(SvNV(*callback_ptr)); } return cos(SvNV(in)); } EOC my $angle = 0.5; my $x = {'Number' => $angle}; bless $x; print wrap_cos($x), "\n"; # Output: 0.877582561890373 print wrap_cos($angle), "\n"; # Output: 0.877582561890373
    Inline::C is just XS under the hood, and the XS file that it autogenerates and compiles (and can be found under ./_Inline/build) is:
    #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "INLINE.h" double wrap_cos(SV * in) { if(sv_isobject(in)) { HV *self_hv = MUTABLE_HV(SvRV(in)); SV **callback_ptr = hv_fetchs(self_hv, "Number", 0); return cos(SvNV(*callback_ptr)); } return cos(SvNV(in)); } MODULE = try_pl_96f2 PACKAGE = main PROTOTYPES: DISABLE double wrap_cos (in) SV * in
    Maybe there's something there that helps you ... or maybe not ;-)
    (The header file INLINE.h is generally not necessary, and is not needed here. You'll find it in the same folder as the XS file.)

    Cheers,
    Rob
      I implemented your snippet in my XS and it worked properly. Then I started again to test why my code didn't work. It was simply a problem of declaration: if I declare the SV * zahl inside the if-else statement the compiler somehow doesn't get that the variable will be declared anyways and starts to throw a massive amount of errors at me. Your code did work because you avoided the declaration of this additional SV *. Somehow I feel quite dumb now. Thanks for the help, all!