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.