c++perlxs

perl xs - can't return a new custom c++ object from method call - returns scalar value instead


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?


Solution

  • 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.