Perl入門ゼミ

テキスト処理、Linuxサーバー管理、Web開発ならPerl
  1. Perl
  2. XS
  3. here

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
Qiitaで
「3分間Perlテキストクッキング」
という連載を始めました。
テキスト処理を題材にして、3分くらいで読める分量で、書いていきます。
文字コード、テキストデータ、コンピュータにおけるテキストの扱いなど、ソフトウェアの基礎の話題も
3分間Perlテキストクッキング