Perl入門ゼミ

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

perlapi - XSにおけるデータ操作関数

perlapiとはXSでPerlのデータを操作するための公式APIで、C言語で書かれたマクロや関数のことです。

出力、スカラ、標準入出力

PerlとXS(C言語)のAPIの対応がわかるとXSがわかりやすそうだ。

標準出力への出力

#Perl
print "Hello";

# XS
PerlIO_printf(PerlIO_stdout(), "Hello");

PerlIO_printfはC標準ライブラリのprintfにあたる関数。第一引数はPerlIO*型の変数、第二引数はchar*型の変数。標準出力はPerlIO_stdout関数で取得できる。XSでは標準入出力Cの標準ライブラリは使わずに、Perlで用意されている関数を使う。これはperlapioというドキュメントに詳しく乗せられている。

文字列

#Perl
my $str = "Hello";

# XS
SV* sv_str = newSVpv("Hello", 0);

PerlのスカラはXSではSV*に対応します。newSVpv関数で文字列を表現するSV*を作成することができます。第一引数は、文字列です。第二引数はSTRLEN型の引数で、文字列の長さを指定します。0を指定すると自動的に文字列の長さを決定してくれます。

この他のものとして、効率を重視した、newSVpvn関数やnewSVpvs関数もあります。

C言語のAPIは省略が多いので覚えるのが大変ですが、慣れるまでがんばるしかないです。SVのSはスカラという意味、Vは値(value)という意味です。newSVpvのpはポインタ(pointer)という意味です。これは実質的には文字列を表します。ですからpは文字列と覚えます。

数値

# 符号なし整数
# Perl
my $num = 1;

#XS
SV* sv_num = newSVuv(5);
# 整数
# Perl
my $num = -1;

#XS
SV* sv_num = newSViv(-1);
# 数値
# Perl
my $num = 1.14;

#XS
SV* sv_num = newSVnv(1.14);

Perlでは数値はすべてどのようなものでも浮動小数点として扱われますが、内部的に見ると符号なし整数(UV型)、整数(IV型)、数値型(NV型)の三種類がある。数値型が最も汎用性があるが、効率は符号なし整数や整数のほうがよい。これらはSV*の内部的な値として保持されます。UVのUは「unsigned int」のuです。IVのIはintのiです。NVのNはnumberのnです。

未定義値(undefに対応)

XSではPerlのundefは、&PL_sv_undefで表現します。

# Perl
my $val = undef;

# XS
SV* sv_val = &PL_sv_undef;

値が定義されているかどうかのチェック(defined関数に対応)

SV*型の値が定義されているかどうかを確認するには、SvOK関数を使用します。これはPerlのdefined関数相当のことをします。

# Perl
if (defined $val) { ... }

# XS
if (SvOK(sv_val)) { ... }

SV*からchar*型への変換

SV*に文字列や数値が含まれている場合はそれを他の関数などで使う場合に、char*型にする必要があります。

char* str = SvPV_nolen(sv_some);

SvPV_nolen関数の第一引数はSV*型の変数です。長さについては自動的に計算してくれます。

ファイル入出力

#Perl
sub print_line {
  my $file = shift;
  
  open my $fh, '<', $file
    or die $!;
  
  while (my $line = <$fh>) {
    print $line;
  }
  
  close $fh;
}

#XS
SV*
print_line(...)
  PPCODE:
{
    char* file = SvPV_nolen(ST(0));
    PerlIO* infh = PerlIO_open(file, "r");
    SV* line = sv_2mortal(newSVpv("", 0));
    
    if (!infh) {
      croak(strerror(errno));
    }
    
    while(sv_gets(line, infh, 0)) {
      PerlIO_printf(PerlIO_stdout(), SvPV_nolen(line));
    }
    PerlIO_close(infh);
}

難しい。ファイル入出力ってこれであっているのかなぁ。

SV*は揮発性にしておく

SV*を作成したときは必ずsv_2mortalして揮発性にしておく。そうするとスコープを抜けた位置で自動的に解放される。

XSでメモリリークをしないようにする記述方法は以下で解説しています。

Perlでの操作に対応するAPIを発見する

XSを書くためにはいつもはPerlで行っている操作をC言語のAPIで行う必要がある。これはなかなか大変だが、perlapiとperlapioに公開されているAPIがすべて載っているのでここから探す。Perlの内部を解説したperlgutsも参考になる。

配列操作

配列の操作に対応するXSでの書き方。

変数宣言。

# Perl
my @nums;

# XS
AV* av_nums = newAV();

代入。

# Perl
$nums[0] = 1;

# XS
av_store(av_nums, 0, newSVuv(1));

要素の取得。av_fetchはSV*へのポインタを返すので*でポインタはずしを行う必要があります。第三引数は要素が存在しなかった場合に作成するかどうかを指定します。av_fetchは要素がなかった場合にNULLを返すので確認してから参照はずしを行う必要があります。

# Perl
my $num = $nums[0];

# XS
SV** const sv_num_ptr = av_fetch(nums, 0, FALSE);
SV* const sv_num = num_ptr ? *sv_num_ptr : &PL_sv_undef;

配列のサイズ。

# Perl
my $count = @nums;

# XS(歴史的事情で長さより1小さい値が返ってくるので注意)
int count = av_len(nums) + 1;

push関数。

# Perl
push @nums, 1;

# XS
av_push(av_nums, newSVuv(1));

pop関数。

# Perl
my $num = pop @nums;

# XS
SV* sv_num = av_pop(av_nums);

shift関数。

# Perl
my $num = shift @nums;

# XS
SV* sv_num = av_shift(av_nums);

unshift関数。av_unshiftは値を先頭につけ加えるのではなく、先頭に空の領域を追加するだけなので、av_storeで値を設定する必要があります。

# Perl
unshift @nums, 1;

# XS
av_unshift(av_nums, 1);
av_store(av_nums, 0, newSVuv(1));

配列を空に。

# Perl
@nums = ();

# XS
av_clear(av_nums);

ハッシュ操作

ハッシュの操作に対応するXSでの書き方。

キーの存在確認

# Perl
exists $hash{"key"};

# XS
hv_exists(hash, "key", strlen("key"));

ハッシュの配列の作成

XSを最近練習していて、とりあえず次の方針で作成しているが、あっているかはよくわからない。

  1. 戻り値と引数は一行で書いてしまう
  2. PPCODEセクションのみを使う。
  3. ソースコードは先頭で変数宣言ができるように {} で囲む
  4. 戻り値はSV*ひとつだけにする。ST(0)に代入してから、XSRETURN(1)。RETVALを使っていないという警告は無視する。
  5. SV*、AV*、HV*は作成した直後にsv_2mortalを実行する。これを行わないとメモリリークが発生します。SV*を作成してsv_2mortalしなくてよいのはハッシュや配列の要素にする場合のみ。

XSを書くときにしばらくこういう書き方をしてみることにする。XSの文法はかなり複雑なのでできるだけシンプルかつ一貫性のある書き方をしたい。これでどれくらいの場合にうまくいくかをしばらく試してみる。

ハッシュの配列の作成

ハッシュの配列の作成です。

# 最もシンプルなPerl記法
sub return_array_of_hash {
    
  my $persons = [
      {name => 'Ken',  age => 19},
      {name => 'Taro', age => 16}
  ];
  
  return $persons;
}

# XSの書き方対応させたPerl記法
sub return_array_of_hash {
    
  my @persons;
 
  my %person1;
  $person1{name} = 'Ken';
  $person1{age}  = 19;
  push @persons, \%person1;
  
  my %person2;
  $person2{name} = 'Taro';
  $person2{age}  = 26;
  push @persons, \%person2;
  
  return \@persons;
}

# XS
SV* return_array_of_hash()
    PPCODE:
{
  AV* av_persons;
  HV* hv_person1;
  HV* hv_person2;
  
  av_persons = (AV*)sv_2mortal((SV*)newAV());
  
  hv_person1 = (HV*)sv_2mortal((SV*)newHV());
  hv_store(hv_person1, "name", 4, newSVpv("Ken", 3), 0);
  hv_store(hv_person1, "age", 3, newSVuv(19), 0);
  av_push(av_persons, newRV_inc((SV*)hv_person1));
  
  hv_person2 = (HV*)sv_2mortal((SV*)newHV());
  hv_store(hv_person2, "name", 4, newSVpv("Taro", 4), 0);
  hv_store(hv_person2, "age", 3, newSVuv(26), 0);
  av_push(av_persons, newRV_inc((SV*)hv_person2));
  
  SV* sv_ret = sv_2mortal(newRV_inc(av_persons));
  xPUSHs(sv_ret);
  XSRETURN(1);
}

AV*は配列、HV*はハッシュ。newAVは空の配列の作成。newHVは空のハッシュの作成。hv_storeはハッシュの値の設定、第三引数はキーの長さ、第5引数は0を指定するとハッシュ値を自動で計算される。newRV_incはリファレンスを作成する関数。XSRETURNの引数は戻り値の個数。戻り値はXPUSHsマクロでスタックに積む。

ハッシュの配列の作成その2

gfxさんのアドバイスを元にしてハッシュの配列の作成を書き直してみました。SV*,AV*,HV*を作った直後に必ずmortalにするという原則、文字列を作成するとき、ハッシュの要素を設定するときに文字数を指定しないようにすること、配列やハッシュの要素にするときはリファレンスカウントをひとつ増やさないといけないこと。あとはできるだけ一貫してシンプルに書くことができるようなマクロを定義してみました。どうかなぁ。

#define new_mAV() (AV*)sv_2mortal((SV*)newAV())
#define new_mHV() (HV*)sv_2mortal((SV*)newHV())
#define new_mSVpvs(s) sv_2mortal(newSVpvs(s))
#define new_mSVuv(u) sv_2mortal(newSVuv(u))
#define new_mSViv(i) sv_2mortal(newSViv(i))
#define new_mSVnv(n) sv_2mortal(newSVnv(n))
#define new_mRV(sv) sv_2mortal(newRV_inc((SV*)sv))
#define set(e) SvREFCNT_inc(e)

MODULE = ExtModule		PACKAGE = ExtModule		

SV* return_array_of_hash()
  PPCODE:
{
  AV* persons;
  HV* person1;
  HV* person2;
  
  persons = new_mAV();
  
  person1 =new_mHV();
  hv_stores(person1, "name", set(new_mSVpvs("Ken")));
  hv_stores(person1, "age", set(new_mSVuv(19)));
  hv_stores(person1, "height", set(new_mSViv(170)));
  hv_stores(person1, "weight", set(new_mSVnv(45.3)));
  av_push(persons, set(new_mRV(person1)));
  
  person2 = new_mHV();
  hv_stores(person2, "name", set(new_mSVpvs("Taro")));
  hv_stores(person2, "age", set(new_mSVuv(26)));
  hv_stores(person2, "height", set(new_mSViv(180)));
  hv_stores(person2, "weight", set(new_mSVnv(39.3)));
  av_push(persons, set(new_mRV(person2)));
  
  ST(0) = new_mRV(persons);
  XSRETURN(1);
}

文字列操作

XSにおける文字列操作です。

文字列の作成

# Perl
my $str = "abc";

# XS
SV* sv_str = newSVpv("abc", 0);

文字列のコピー

# Perl
my $str2 = $str1;

# XS
SV* sv_str2 = newSV(sv_str1);

文字列の長さ

# Perl
my $length = length $str;

# XS
STRLEN sv_length = sv_len(sv_str);

文字列の連結

# Perl
my $str = "abc";
$str .= "de";

# XS
SV* sv_str = newSVpv("abc", 0);
sv_catpv(sv_str, "de");

変数展開(あるいはsprintf)

# Perl
my $num1 = 1;
my $num2 = 2;
my $str  = "$num1 and $num2";

# XS
int num1 = 1;
int num2 = 2;
SV* sv_str = newSVpvf("%d and %d", num1, num2);

文字列の連結+変数展開

# Perl
my $num1 = 1;
my $num2 = 2;
my $str = "abc";
$str .= "$num1 and $num2";

# XS
int num1 = 1;
int num2 = 2;
SV* sv_str = newSVpv("abc", 0);
sv_catpvf(sv_str, "%d and %d", num1, num2);

文字列の比較

SVである文字列を比較したい場合はsv_cmpを使用します。

# perl
if ($str1 lt $str2) { ... }
if ($str1 eq $str2) { ... }
if ($str1 gt $str2) { ... }

# XS
if (sv_cmp(sv_str1, sv_str2) < 0) { ... }
if (sv_cmp(sv_str1, sv_str2) == 0) { ... }
if (sv_cmp(sv_str1, sv_str2) > 0) { ... }

またchar*型として比較できる関数strEQ, strEQ, strGE, strGT, strLE, strLT, strNEも用意されています。

if (strEQ("foo", "bar")) { ... }
if (strGE("foo", "bar")) { ... }
if (strGT("foo", "bar")) { ... }
if (strLE("foo", "bar")) { ... }
if (strLT("foo", "bar")) { ... }
if (strNE("foo", "bar")) { ... }

パッケージ変数

XSにおけるパッケージ変数の操作のための関数を紹介します。

パッケージ変数の取得

パッケージ変数を取得するには次のように書きます。

# Perl
$Foo::bar;
@Foo::bar;
%Foo::bar;

# XS
SV* sv_var = get_sv("Foo::bar", 0);
AV* av_var = get_av("Foo::bar", 0);
HV* hv_var = get_hv("Foo::bar", 0);

第二引数の0は新しく変数を作成しないことを意味します。

パッケージ変数の作成]

XSでパッケージ変数を作成する関数を紹介します。get_sv, get_av, get_hvの第二引数で「GV_ADD」を指定します。

# Perl
our $Foo::bar;
our @Foo::bar;
our %Foo::bar;

# XS
SV* sv_var = get_sv("Foo::bar", GV_ADD);
AV* av_var = get_av("Foo::bar", GV_ADD);
HV* hv_var = get_hv("Foo::bar", GV_ADD);

正規表現を利用する

XS正規表現を使う方法を解説します。ただし、現在、Perlのドキュメントに使用方法が解説されていないので、試し試しでやっているので、間違っていたら教えてください。

正規表現のコンパイル

XSで正規表現を使用するにはまずpregcomp関数を使って、正規表現をコンパイルする必要があります。これは、正規表現のリファレンスを作成する操作に対応すると考えてください。

# Perl
my $re = qr/[0-9]+/;

# XS (pregcompの第二引数は正規表現のフラグ)
SV* sv_re_str = newSVpv("[0-9]+", 0);
REGEXP* sv_re = pregcomp(sv_re_str, 0);

REGEXP*型は、SV*型の一種ですので、実際にコードを利用するときはsv_2mortalを使って、自動的にメモリが解放されるようにしましょう。実際のコードで利用するのを想定して書くと次のようになります。

SV* sv_re_str = sv_2mortal(newSVpv("[0-9]+", 0));
REGEXP* sv_re = (REGEXP*)sv_2mortal((SV*)pregcomp(sv_re_str, 0));

正規表現のフラグ

正規表現のフラグを、pregcompの第二引数に指定することができます。

# Perl
my $re = qr/abc/im;

# XS
SV* sv_re_str = newSVpv("[0-9]+", 0);
REGEXP* sv_re = pregcomp(sv_re_str, RXf_PMf_FOLD | RXf_PMf_MULTILINE);

フラグの対応は以下です。複数指定する場合はビット演算子の論理和「|」で、フラグをつなぎます。

/m RXf_PMf_MULTILINE
/s RXf_PMf_SINGLELINE
/i RXf_PMf_FOLD
/x RXf_PMf_EXTENDED

正規表現の実行

正規表現を実行するにはpregexec関数を使用します。pregexec関数の引数は、とても複雑です。

/* pregexec - 文字列に対して正規表現をマッチさせる。 */
I32
pregexec(
  REGEXP * const prog, /* コンパイルされた正規表現 */
  char* stringarg,     /* 文字列のマッチを開始する位置 */
  char *strend,        /* 文字列の終端(NULLポインタの位置) */
  char *strbeg,   /* 文字列の開始位置 */
  SSize_t minend, /* stringarg後のマッチの終端がminend以上でなければならないバイト数 */
  SV *screamer,   /* 文字列をあらわすSV: utf8フラグのためだけに利用される */
  U32 nosave      /* キャプチャしない場合は1を設定 */
)

マッチが成功した場合は戻り値に真が返ります。サンプルを記述しておきます。

SV* sv_value = sv_2mortal(newSVpv("  12", 0));
char* value = SvPV_nolen(sv_value);

SV* sv_re_str = sv_2mortal(newSVpv("^ *([-+]?[0-9]+) *$", 0));
REGEXP* sv_re = (REGEXP*)sv_2mortal((SV*)pregcomp(sv_re_str, 0));

IV ret = pregexec(
  sv_re, // コンパイルされた正規表現
  value, // 検索開始位置
  value + strlen(value), // 文字列の終端(NULLポインタ)
  value, // 文字列の頭
  0, // 0でOK
  sv_value, // SV*型の文字列
  0 // 0でOK
);

マッチした文字列の取得

マッチした文字列を取得するにはPerl_reg_numbered_buff_fetch関数を使用します。(非公開のAPIしかないのかな)

# Perl
my $match1 = $1;
my $match2 = $2;

# XS
SV* sv_match1 = newSVpv("", 0);
Perl_reg_numbered_buff_fetch(aTHX_ sv_re, 1, match1); 

SV* sv_match2 = newSVpv("", 0);
Perl_reg_numbered_buff_fetch(aTHX_ sv_re, 2, match2); 

オブジェクト指向関連のAPI

オブジェクト指向に関連したXSのAPIを紹介します。

オブジェクトを生成する

オブジェクトを生成します。必ずモータルにしておきましょう。

# Perl
my $self = {};
bless $self, "MyClass";

# XS
SV* sv_self = sv_2mortal(new_RV_inc(sv_2mortal((SV*)newHV()));
sv_bless(sv_self, gv_stashpv("MyClass", 1));

オブジェクトであるかどうかを確認する

値がオブジェクトであるかどうかを確認するには、sv_isobject関数を使用します。

sv_isobject(sv_obj);

あるクラスを継承しているかどうかを確認する

あるクラスを継承しているかどうかを確認するにはsv_derived_from関数を使用します。指定したクラスを継承している場合は、真が返ってきます。

sv_derived_from(sv_obj, "MyClass");

この関数は、配列のリファレンスであることや、ハッシュのリファレンスであることを確認するのに利用することも可能です。

/* 配列のリファレンス */
sv_derived_from(sv_obj, "ARRAY");

/* ハッシュのリファレンス */
sv_derived_from(sv_obj, "HASH");

ですので、オブジェクトであり、特定のクラスを継承していることを確認するには、sv_isobject関数とsv_derived_from関数を組み合わせて利用します。

sv_isobject(sv_obj) && sv_derived_from(sv_obj, "MyClass")

Perl APIの公式ドキュメント

  • Perlとはテキスト処理の記述性とパフォーマンスに優れ、正規表現が言語に組み込まれているプログラミング言語です。
  • Linuxサーバーでのフィルタリングプログラム、複数行の文字列を処理、ファイル内容の検索・置換などが得意
  • Perlはgitopensslなど広く普及したUnix/Linuxミドルウェアの補助ツールとして採用実績あり。後方互換性とポータビリティの高さがひとつの理由と推測。
  • 大量のテキストを扱うWeb開発も得意。ロングテールSEOを意識したWebサイト、アドテクやソーシャルゲームでの50ms以内のJSONの生成など。