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

1

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?

Maitilde answered 10/10, 2017 at 13:3 Comment(11)
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.Baronetage
Could you edit the question to show your typemap for matrix* as well?Baronetage
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.Maitilde
@Baronetage Just added the typemap.Maitilde
PS - Why isn't new a method?!Kick
@Kick I'm not sure what you mean?Maitilde
One normally uses matrix->new(...); to create an object.Kick
PS - Lowercase module names are reserved.Kick
PS - Lowercase module names indicate pragmas. Your module is not a pragma.Kick
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 );!!!Kick
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++.Maitilde
B
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.

Baronetage answered 10/10, 2017 at 13:24 Comment(3)
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.Maitilde
@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.Kick
@Kick 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.Baronetage

© 2022 - 2024 — McMap. All rights reserved.