7

In my XS file I have:

As my new method:

matrix *
matrix::new( size_t ncols, size_t nrows )

which returns a matrix object like it should and I can invoke methods.

Then I have a method call which creates a new matrix object and is supposed to return it as a new matrix:

matrix *
matrix::getInnerMatrix( )
    PREINIT:
        char *  CLASS = (char *)SvPV_nolen(ST(0));
    CODE:
        RETVAL = static_cast<matrix*>(THIS->matrix::getInnerMatrix());
    OUTPUT:
        RETVAL

However the returned type is matrix=SCALAR(0x122f81c) and therefore I am unable to invoke any method calls from this object as the perl interpreter seems to be viewing the returned type as a scalar value type instead of a 'matrix' object. Here is a test script:

$m1 = matrix::new(matrix,4,4);
@arr = ( 1 .. 16 );
$aref = [@arr];
$m1->assign_aref($aref);
my $m2 = $m1->getInnerMatrix();
print ref $m1; # returns "matrix" (like it should)
print "\n\n";
print ref $m2; # returns "matrix=SCALAR(0x122f81c)" (wrong)

Here is my typemap:

TYPEMAP
matrix *        O_MATRIX

OUTPUT
O_MATRIX
    sv_setref_pv( $arg, CLASS, (void*)$var );

INPUT
O_MATRIX
    if ( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) ) {
        $var = ($type)SvIV((SV*)SvRV( $arg ));
    }
    else {
        warn( \"${Package}::$func_name() -- ${var} not a blessed SV reference\" );
        XSRETURN_UNDEF;
    }

What changes must I make in my XS file, or any other file to ensure that a pure matrix object is returned?

  • 1
    Why are you explicitly assigning the `CLASS`? Note that `SvPV_nolen(scalar)` works pretty much like `"$scalar"`, i.e. stringifies the reference which by default looks like `ClassName=HASH(0xabc123)` or similar. – amon Oct 10 '17 at 13:08
  • 2
    Could you edit the question to show your typemap for `matrix*` as well? – amon Oct 10 '17 at 13:12
  • I added the line `char * CLASS = (char *)SvPV_nolen(ST(0));` because before, when I didn't have it in there, I was getting the compiler error `error: 'CLASS' was not declared in this scope sv_setref_pv( RETVALSV, CLASS, (void*)RETVAL );`. After adding that line, it compiles. –  Oct 10 '17 at 13:14
  • @amon Just added the typemap. –  Oct 10 '17 at 13:15
  • PS - Why isn't `new` a method?! – ikegami Oct 10 '17 at 13:46
  • @ikegami I'm not sure what you mean? –  Oct 10 '17 at 16:16
  • One normally uses `matrix->new(...);` to create an object. – ikegami Oct 10 '17 at 16:17
  • PS - Lowercase module names are reserved. – ikegami Oct 10 '17 at 16:18
  • PS - Lowercase module names indicate pragmas. Your module is not a pragma. – ikegami Oct 10 '17 at 16:18
  • I think your XS for `new` is correct. You just need to replace `$m1 = matrix::new(matrix,4,4);` with `my $m1 = matrix->new(4,4);`. Your version doesn't even work with `use strict;`. **Always use `use strict; use warnings qw( all );`!!!** – ikegami Oct 10 '17 at 16:22
  • Thanks for telling all that. I'll will make those changes. I'm still new to perl and xs. My background is extensively in C++. –  Oct 11 '17 at 13:08

1 Answers1

4

When using XS with C++, the XS preprocessor inserts THIS for instance methods and CLASS for static methods. A method called new is treated as a static method. This allows the resulting xsubs to be used as instance methods/class methods by default: matrix->new and $m->getInnerMatrix().

Your typemap uses the CLASS variable which is not provided for instance methods. In your case, I would hard-code the package name in the type map instead:

OUTPUT
O_MATRIX
    sv_setref_pv( $arg, "matrix", (void*)$var );

The typemap is also used when an argument of that type is not used as the invocant. E.g. consider this xsub:

matrix*
some_other_xsub(x)
    int x

Here there would not by a CLASS variable for the matrix* return value either.

Note that lowercase package names should only be used for pragma packages (like strict or warnings). Please use CamelCase for your classes.

Your attempt to provide your own value for CLASS failed because SvPV_nolen() stringifies the reference and does not get the reference type. I.e. it's equivalent to "$m", not to ref $m. A more correct alternative would have been to use sv_ref():

char* CLASS = SvPV_nolen(sv_ref(NULL, THIS, true));

The third parameter to sv_ref() makes this function work like the Perl function ref, i.e. return the class name if the scalar is blessed, not just the underlying reference type.

amon
  • 57,091
  • 2
  • 89
  • 149
  • After I changed `sv_setref_pv( $arg, CLASS, (void*)$var );` to `sv_setref_pv( $arg, "matrix", (void*)$var );` in my typemap file, I no longer needed to add that extra line in the the `xs` file. –  Oct 10 '17 at 13:39
  • @amon, Hardcoding `matrix` prevents inheritance. Leaving the typemap unchanged and using your later solution (`char* CLASS = SvPV_nolen(sv_ref(NULL, THIS, true));`) is best. – ikegami Oct 10 '17 at 16:21
  • @ikegami inheritance across language boundaries is quite tricky, and I would strongly recommend against it if possible. I'd rather treat XS-wrapped classes as final than deal with the possible failure modes. You are right though that at least in the constructor, depending on `CLASS` would be preferable. But for non-constructor functions, blessing return values into a subclass would sidestep subclass constructors which might be a bigger problem. – amon Oct 10 '17 at 16:33