XSでC言語の構造体をPerlのオブジェクトとして扱う方法
XSでは、構造体自体をPerlのオブジェクトとして扱うこともできます。C言語の構造体をPerlのオブジェクトとして扱う方法を解説します。
h2xsでモジュールを作成
最初にh2xsでXS用のモジュールを作成します。
h2xs -A -n SomeModule
こうすると「SomeModule」というディレクトリが作成されます。次のようなファイルとディレクトリが作成されます。
Changes lib/ Makefile.PL MANIFEST ppport.h README SomeModule.xs t/
XSファイルの記述
XSファイルを記述しましょう。構造体のポインタをPTR2INTでsize_t型に変換しています。さらに、size_t型をSV*型に変換し、SV*型をSV*型へのリファレンスに変換し、最後にblessして、オブジェクトに変換しています。
size_t型というのは整数型ですが、アドレスの値はsize_t型で受け取るようにします。
取り出すときは、デリファレンスを行い、SV*に含まれるIVの値を取り出し、INT2PTRで構造体へのポインタに変換しています。
#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" typedef struct { double x; double y; } Point; MODULE = Point PACKAGE = Point void new(...) PPCODE: { // クラス名 char* class_name = SvPV_nolen(ST(0)); // xとy double x = SvNV(ST(1)); double y = SvNV(ST(2)); // 構造体の作成(ポインタとして作成) Point* point = (Point*)malloc(sizeof(Point)); point->x = x; point->y = y; // ポインタをsize_t型に変換 size_t point_iv = PTR2IV(point); // size_t型をSV*型に変換 SV* point_sv = sv_2mortal(newSViv(point_iv)); // SV*型のリファレンスを作成 SV* point_svrv = sv_2mortal(newRV_inc(point_sv)); // オブジェクトを作成 SV* point_obj = sv_bless(point_svrv, gv_stashpv(class_name, 1)); XPUSHs(point_obj); XSRETURN(1); } void x(...) PPCODE: { // オブジェクトを取得 SV* point_obj = ST(0); // デリファレンス SV* point_sv = SvROK(point_obj) ? SvRV(point_obj) : point_obj; // SV*型をsize_t型に変換 size_t point_iv = SvIV(point_sv); // size_t型をポインタに変換 Point* point = INT2PTR(Point*, point_iv); // xを取得 double x = point->x; // xをSV*型に変換 SV* x_sv = sv_2mortal(newSVnv(x)); XPUSHs(x_sv); XSRETURN(1); } void y(...) PPCODE: { // オブジェクトを取得 SV* point_obj = ST(0); // デリファレンス SV* point_sv = SvROK(point_obj) ? SvRV(point_obj) : point_obj; // SV*型をsize_t型に変換 size_t point_iv = SvIV(point_sv); // size_t型をポインタに変換 Point* point = INT2PTR(Point*, point_iv); // xを取得 double y = point->y; // xをSV*型に変換 SV* y_sv = sv_2mortal(newSVnv(y)); XPUSHs(y_sv); XSRETURN(1); } void DESTORY(...) PPCODE: { // オブジェクトを取得 SV* point_obj = ST(0); // デリファレンス SV* point_sv = SvROK(point_obj) ? SvRV(point_obj) : point_obj; // SV*型をsize_t型に変換 size_t point_iv = SvIV(point_sv); // size_t型をポインタに変換 Point* point = INT2PTR(Point*, point_iv); // Point*を解放 free(point); XSRETURN(0); } MODULE = SomeModule PACKAGE = SomeModule
Pointモジュールの作成
Point.pmというファイルをlib以下においてください。SomeModuleを読み込んでいるのは、SomeModuleにバインディングの記述があるためにです。
package Point; use SomeModule; 1;
テストスクリプト
テストスクリプトを作成します。これは、XSファイルがあるディレクトリと同じディレクトリにおいてください。
use strict; use warnings; use Point; my $point = Point->new(1, 2); print $point->x . "\n"; print $point->y . "\n";
コンパイルして実行
コンパイルして実行してみましょう。
perl Makefile.PL make perl -Mblib test.pl
次のように出力されれば成功です。
1 2