C++のクラスをXSから利用する方法
XSでC++のクラスを呼び出す方法を学びましょう。これができるようになれば、どんなC++のライブラリでも、Perlにバインディングができるようになると思います。
h2xsでモジュールを作成
最初にh2xsでXS用のモジュールを作成します。
h2xs -A -n MyClass
こうすると「MyClass」というディレクトリが作成されます。次のようなファイルとディレクトリが作成されます。
Changes lib/ Makefile.PL MANIFEST ppport.h README MyClass.xs t/
C++でクラスの作成
C++でクラスを作成しましょう。ヘッダファイルとソースファイルを作成します。
MyClass.h
ヘッダファイルです。コンストラクタ、メソッド、クラスメソッドを宣言しています。通常のメソッドとクラスメソッドの呼び出し方の違いを理解するために、このような構成にしています。ファイル名は「MyClass.h」です。
#ifndef MYCLASS_INCLUDE #define MYCLASS_INCLUDE class MyClass { public: // コンストラクタ MyClass(); // メソッド void print(); // クラスメソッド static void print_static(); }; #endif
「#ifndef」で始まっているのは、インクルードガードと呼ばれるものです。ヘッダファイルは、XSファイルと、ソースファイルの2箇所から読み込まれる必要があります。すると、そのままでは、ヘッダファイルが二重に取り込まれて、コンパイルエラーになってしまいます。それを防ぐために、インクルードを行っています。
MyClass_src.cpp
C++のソースファイルです。コンストラクタ、メソッド、クラスメソッドの実装を行っています。ソースファイルでは、MyClassというシンボルを解決するために、ヘッダファイルを読み込む必要があります。ファイル名は「MyClass_src.cpp」にしてあります。これは「MyClass.cpp」としてしまうと、コンパイル後の名前は「MyClass.o」となりますが、これはXSファイル「MyClass.xs」のコンパイル後の名前「MyClass.o」とかぶってしまうためです。
#include <iostream> #include "MyClass.h" // コンストラクタ MyClass::MyClass() {} // メソッド void MyClass::print() { std::cout << "MyClass::print\n"; } // クラスメソッド void MyClass::print_static() { std::cout << "MyClass::print_static\n"; }
ヘッダファイルとソースファイルは保存して、XSファイルが存在するディレクトリと同じディレクトリに配置してください。
XSファイルの記述
XSファイルを記述しましょう。
#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; }
型の変換を行うためのマクロ
少し解説をしておきます。以下は、Perlのオブジェクト(SV*型)をC(あるいはC++)のポインタに変換するマクロと、C(あるいはC++)のポインタをPerlのオブジェクト(SV*型)に変換するマクロになっています。
#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) \ );
コンストラクタ
コンストラクタです。newを使ってMyClassをインスタンス化してMyClass*型に代入します。そして、このポンイタを、Perlのオブジェクト変換して、返します。
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); }
メソッド呼び出しでは、第一引数にはPerlのオブジェクトが渡ってくるので、これをポインタ「MyClass*」に変換します。そして「self->print」としてメソッドを呼び出します。
クラスメソッドの呼び出し
クラスメソッドの呼び出しです。
void print_static(...) PPCODE: { MyClass::print_static(); XSRETURN(0); }
「MyClass::print_static()」と完全修飾名で呼び出しているだけです。
デストラクタ
デストラクタです。deleteを使って、メモリの開放を行う必要があります。
void DESTORY(...) PPCODE: { MyClass* self = XS_OBJ_TO_PTR(ST(0), MyClass*); delete self; }
Makefile.PLの修正
次にMakefile.PLを少し修正しましょう。一番下の「OBJECT」オプションがデフォルトではコメントアウトされているので、コメントを取り除きます。「$(O_FILES)」という設定をすれば、カレントディレクトリのすべてのC言語のソースファイルとC++のソースファイルがコンパイルの対象になります。
そしてコンパイラとリンカを「g++」に変更します。コンパイラは「CC」オプション、リンカは「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++', );
テストスクリプト
テストスクリプトを作成します。これは、XSファイルがあるディレクトリと同じディレクトリにおいてください。
use strict; use warnings; use MyClass; my $obj = MyClass->new; $obj->print; MyClass->print_static;
コンパイルして実行
コンパイルして実行してみましょう。
perl Makefile.PL make perl -Mblib test.pl
次のように出力されれば成功です。
MyClass::print MyClass::print_static
これで、C++のクラスをXSファイルから利用することができるようになりました。