1. Perl
  2. XS
  3. here

How to use C ++ classes from XS

Learn how to call C ++ classes on your XS. If I can do this, I think any C ++ library will be able to bind to Perl.

Create a module with h2xs

First, create a module for XS with h2xs.

h2xs -A -n MyClass

This will create a directory called "MyClass". The following files and directories will be created.

Changes
lib/Makefile.PL
MANIFEST
ppport.h
README
MyClass.xs
t/</pre>

<h3>Creating a class in C ++</h3>

Let's create a class in C ++. Create a header file and a source file.

<b> MyClass.h </b>

This is a header file. Declares constructors, methods and class methods. This is done to understand the difference between calling regular methods and class methods. The file name is "MyClass.h".

<pre>
# ifndef MYCLASS_INCLUDE
# define MYCLASS_INCLUDE
class MyClass {
  
  public: public:
  
  // constructor
  MyClass ();
  
  // method
  void print();
  
  // class method
  static void print_static ();
};
# endif

What starts with "#ifndef" is what is called an include guard. The header file needs to be read from two places, the XS file and the source file. Then, if it is left as it is, the header file will be imported twice and a compile error will occur. To prevent that, we are including it.

MyClass_src.cpp

C ++ source file. Implements constructors, methods, and class methods. In the source file, you need to read the header file to resolve the symbol MyClass. The file name is "MyClass_src.cpp". This is because if you name it "MyClass.cpp", the compiled name will be "MyClass.o", because it will overlap with the compiled name "MyClass.o" of the XS file "MyClass.xs". ..

# include <iostream>
# include "MyClass.h"

// constructor
MyClass::MyClass () {}

// method
void MyClass::print() {
  std::cout << "MyClass::print\n";
}

// class method
void MyClass::print_static () {
  std::cout << "MyClass::print_static\n";
}

Save the header and source files and place them in the same directory where the XS files are located.

XS file description

Let's write an XS file.

# include "EXTERN.h"
# include "perl.h"
# include "XSUB.h"

# include "ppport.h"
# include "MyClass.h"

# define XS_OBJ_TO_PTR (x, type) (INT2PTR (type, SvROK (x)? SvIV (SvRV (x)): SvIV (x)))
# define XS_PTR_TO_OBJ (x, class)\sv_bless(\
    sv_2mortal (\
      newRV_inc (\
        sv_2mortal (\
          newSViv (PTR2IV (x))\        )\      )\    ),\gv_stashpv (class, 1)\  );

MODULE = MyClass PACKAGE = MyClass

void
new(...)
  PPCODE:
{
  MyClass * self = new MyClass ();
  SV * self_sv = XS_PTR_TO_OBJ (self, "MyClass");
  
  XPUSHs (self_sv);
  XSRETURN (1);
}

void
print(...)
  PPCODE:
{
  MyClass * self = XS_OBJ_TO_PTR (ST (0), MyClass *);
  
  self->print();
  XSRETURN (0);
}

void
print_static (...)
  PPCODE:
{
  MyClass::print_static ();
  XSRETURN (0);
}

void DESTORY (...)
  PPCODE:
{
  MyClass * self = XS_OBJ_TO_PTR (ST (0), MyClass *);
  delete self;
}

Macro for type conversion

Let me explain a little. Below are macros that convert Perl objects (SV * type) to C (or C ++) pointers and macros that convert C (or C ++) pointers to Perl objects (SV * type).

# define XS_OBJ_TO_PTR (x, type) (INT2PTR (type, SvROK (x)? SvIV (SvRV (x)): SvIV (x)))
# define XS_PTR_TO_OBJ (x, class)\sv_bless(\
    sv_2mortal (\
      newRV_inc (\
        sv_2mortal (\
          newSViv (PTR2IV (x))\        )\      )\    ),\gv_stashpv (class, 1)\  );

Constructor

It is a constructor. Use new to instantiate MyClass and assign it to the MyClass * type. Then, this Ponita is converted into a Perl object and returned.

void
new(...)
  PPCODE:
{
  MyClass * self = new MyClass ();
  SV * self_sv = XS_PTR_TO_OBJ (self, "MyClass");
  
  XPUSHs (self_sv);
  XSRETURN (1);
}

method call

A method call.

void
print(...)
  PPCODE:
{
  MyClass * self = XS_OBJ_TO_PTR (ST (0), MyClass *);
  
  self->print();
  XSRETURN (0);
}

In the method call, the Perl object is passed as the first argument, so convert it to the pointer "MyClass *". Then call the method as "self->print".

Calling class methods

This is a class method call.

void
print_static (...)
  PPCODE:
{
  MyClass::print_static ();
  XSRETURN (0);
}

I'm just calling "MyClass::print_static ()" with a fully qualified name.

Destructor

It is a destructor. You need to use delete to free the memory.

void DESTORY (...)
  PPCODE:
{
  MyClass * self = XS_OBJ_TO_PTR (ST (0), MyClass *);
  delete self;
}

Modify Makefile.PL

Next, let's modify Makefile.PL a little. The bottom "OBJECT" option is commented out by default, so uncomment it. If you set "$(O_FILES)", all C language source files and C ++ source files in the current directory will be compiled.

Then change the compiler and linker to "g ++". The compiler can be set with the "CC" option, and the linker can be set with the "LD".

use ExtUtils::MakeMaker;
use strict;
use warnings;

WriteMakefile (
    NAME =>'MyClass',
    # VERSION_FROM =>'lib/MyClass.pm', finds $VERSION
    # PREREQ_PM => {}, e.g., Module::Name => 1.1
    # ($] >= 5.005? # Add these new keywords supported since 5.005
      # (ABSTRACT_FROM =>'lib/MyClass.pm', retrieve abstract from module
       AUTHOR =>'A. U. Thor <kimoto@sakura.ne.jp>') :()),
    # LIBS => [''], e.g., '-lm'
     # DEFINE =>'', e.g., '-DHAVE_SOMETHING'
     # INC =>'-I.', e.g., '-I. -I/usr/include/other'
     # OBJECT =>'$(O_FILES)', link all the C files too
     CC =>'g ++',
     LD =>'g ++',
);

Test script

Create a test script. This should be in the same directory where the XS files are located.

use strict;
use warnings;

use MyClass;

my $obj = MyClass->new;
$obj->print;
MyClass->print_static;

Compile and run

Let's compile and run it.

perl Makefile.PL
make
perl -Mblib test.pl

If the output is as follows, it is successful.

MyClass::print
MyClass::print_static

Now you can use C ++ classes from your XS file.(Returns a pointer to the SV * type)

SV ** num_sv_ptr = av_fetch (nums_av, i, FALSE); // Set the value if the element is found, undef if not found SV * num_sv = num_sv_ptr? * num_sv_ptr: & PL_sv_undef; // Convert to type IV IV num = SvIV (num_sv); // Assign to array nums [i] = num; printf("%d\n", num); } // Release when no longer needed free (nums); XSRETURN (0);

}

Convert C language array to array reference

Converts a C array into a Perl array reference. An array (AV * type) can be created with "newAV ()". If you created the array, it should be mortal. Note that sv_2mortal only accepts SV * types, so we cast it as (SV *). Also note that sv_2mortal returns the SV * type, so we cast it as (AV *).

Use av_push to add elements to the array. I am creating a new SV type value from an IV type value and adding it to the array. When adding an SV * type value as an element of an array, you need to manually increase the reference count. We are using SvREFCNT_inc for this.

You can generate a reference with newRV_inc. Since newRV receives an argument of type SV *, it is cast as (SV *). If you create a reference, you also need to make it mortal with sv_2mortal.

void
foo (...)
  PPCODE:
{
  // C language array
  IV nums [] = {1, 2, 3};
  
  // Array length
  size_t nums_len = sizeof nums/sizeof nums [0];
  
  // Perl array
  AV * nums_av = (AV *) sv_2mortal ((SV *) newAV ());
  
  // Add to array
  for (int i = 0; i <nums_len; i ++) {
    av_push(nums_av, SvREFCNT_inc (sv_2mortal (newSViv (nums [i]))));
  }
  
  // Create an array reference
  SV * nums_avrv = sv_2mortal (newRV_inc ((SV *) nums_av));
  
  XPUSHs (nums_avrv);
  XSRETURN (1);
}

Convert hash reference to struct

I will explain how to convert Perl hash to a C language structure.

Declare the structure in the C language section.

typedef struct {
  double x;
  double y;
} Point;

The following is written in the XS section.

void
foo (...)
  PPCODE:
{
  SV * point_hvrv = ST (0);
  HV * point_hv;
  if (SvROK (point_hvrv)) {
    // Dereference and convert to HV * type
    point_hv = (HV *) SvRV (point_hvrv);
  }
  else {
    croak ("first argument must be hash reference");
  }
  
  // Extract the hash element corresponding to x with hv_hetch (returns a pointer to SV * type)
  SV ** x_sv_ptr = hv_fetch (point_hv, "x", strlen ("x"), 0);
  
  // Set the value if the element is found, undef if not found
  SV * x_sv = x_sv_ptr? * X_sv_ptr: & PL_sv_undef;
  
  // Convert to double type
  double x = SvNV (x_sv);
  
  // Use hv_hetch to retrieve the hash element corresponding to y (a pointer to the SV * type is returned)
  SV ** y_sv_ptr = hv_fetch (point_hv, "y", strlen ("y"), 0);
  
  // Set the value if the element is found, undef if not found
  SV * y_sv = y_sv_ptr? * y_sv_ptr: & PL_sv_undef;
  
  // Convert to double type
  double y = SvNV (y_sv);
  
  // Create a pointer to the structure
  Point * point = (Point *) malloc (sizeof (Point));
  point->x = x;
  point->y = y;
  
  printf("%f\n", point->x);
  printf("%f\n", point->y);
  
  // Release when no longer needed
  free (point);
  
  XSRETURN (0);
}

Convert struct to hash reference

Converts the structure to a hash reference.

Declare the structure in the C language section.

typedef struct {
  double x;
  double y;
} Point;

The following is written in the XS section. Hash is created by "newHV ()". Don't forget to make it mortal. When adding to the hash element, SvREFCNT_inc increments the reference count by one.

void
foo (...)
  PPCODE:
{
  // Structure
  Point * point = (Point *) malloc (sizeof (Point));
  point->x = 1;
  point->y = 2;
  
  // Convert value to SV * type
  SV * x_sv = sv_2mortal (newSVnv (point->x));
  SV * y_sv = sv_2mortal (newSVnv (point->y));
  
  // Create hash
  HV * point_hv = (HV *) sv_2mortal ((SV *) newHV ());
  
  // add value to hash
  hv_store (point_hv, "x", strlen ("x"), SvREFCNT_inc (x_sv), 0);
  hv_store (point_hv, "y", strlen ("y"), SvREFCNT_inc (y_sv), 0);
  
  // Create a hash reference
  SV * point_hvrv = sv_2mortal (newRV_inc ((SV *) point_hv));
  
  // Release the structure
  free (point);
  
  XPUSHs (point_hvrv);
  XSRETURN (1);
}

To find out XS functions perlapi

The functions that create SV type data introduced here can be found in a document called perlapi. Please use the brief explanation of perlapi.

Related Informatrion