正月番組をみながら[http://perldoc.perl.org/perlxstut.html#EXAMPLE-3:title=perldoc perlxstut]
を読んだ.メモをとったのでせっかくだからはっつけておくよ.
ちょっとメモがはしょり気味なのと,うちの理解があやしいところがあってこころもとないですが,perlxstut読むときの参考にでもなればばば.
Example1
$ h2xs -A -n Mytest
で雛形がつくれる.生成されたMytest.xsにXSのコードを書く
値を返さないHello, WorldだとCODE:以下にprintfを書くだけでOK
void hello() CODE: printf("Hello, world!\n");
CODE: みたいな部分はXS特有の書き方.あとで展開されてCのコードになる.
$ perl Makefile.PL $ make
するとビルドされる.このとき作られるMytest.cをみるとXSから生成されたCのコードを見れる.
t/Mytest.tにコードを書いてみて実行結果を確認できる.
use Test::More tests => 9; BEGIN { use_ok('Mytest') }; Mytest::hello();
Example2
引数をとって値を返す関数をXSで定義してみる.さっき作ったMytest.xsに以下を追加.
int is_even(input) int input CODE: RETVAL = (input % 2 == 0); OUTPUT: RETVAL
inputで整数値を受け取り,RETVALに結果を代入する.RETVALはXSで容易される返り値のための変数.
OUTPUT: のところに変数を書くと返り値として使われる.
Example3
引数に渡ってきた変数自体を変更することで値を返す破壊的な関数を定義してみる.Mytest.xsに以下を追加.
void round(arg) double arg CODE: if (arg > 0.0) { arg = floor(arg + 0.5); } else if (arg < 0.0) { arg = ceil(arg - 0.5); } else { arg = 0.0; } OUTPUT: arg
このように仮引数argに代入してそのままOUTPUT:で返却すると,argで渡ってきた変数に変更が加えられる.
my $i; $i = -1.5; Mytest::round($i); is $i, -2.0; # ok
ということ.
ちなみに,XS => C変換はXSUBPPというのがやってるらしい.
TYPEMAPについて
TYPEMAPはCの型とPerlの値を自動的にマッピングする仕組み.
typemapファイルに型の対応や変換する方法が書いてある.(macportsのPerl5.8.8だと /opt/local/lib/perl5/5.8.8/ExtUtils/typemap にある)
Example4
Cライブラリのヘッダの情報にモジュールテンプレートを作る.ヘッダを準備してから.
$ h2xs -O -n Mytest2 ./Mytest2/mylib/mylib.h
のようにヘッダを引数に指定して実行.
くわえて,Cのライブラリも一緒にビルドするためのMakefile.PLの書き方の解説など.詳細は割愛.
XSのなかでデフォルトで用意されているもの以外のTYPEMAPが必要な場合は,モジュールディレクトリ直下のtypemapファイルに書く.
const char * T_PV
.xsファイルの構成
MODULE = Mytest2 PACKAGE = Mytest2
xsファイルのこの行より上は普通のCのコードが書ける.この行より下はXSのコードになる.XSのコードはxsubppコマンドでCのコードに変換される.
通常のXSでは,実際に呼び出されるCの関数はこの行より上の部分か,外部のライブラリで定義される.XSではPerlとそのCの関数のGlueだけが定義される.XSにちょくせつ関数の機能を書くのは例外的だ.
XSUBもうちょい詳細
XSはかしこいので,
double foo(a, b, c) int a long b const char *c CODE: RETVAL = foo(a,b,c); OUTPUT: RETVAL
本来このように書かないといけないところが,
double foo(a, b, c) int a long b const char *c
のように省略して書ける.
XSUBの引数についてもうちょっと
int foo(a, b) char &a char * b
XSで&aとかくと,Cの関数にaではなくaのアドレスがわたるようになる.ポインタをわたすときは*とbの間にスペースが必要なので注意.
引数スタック
生成されたCのコードからはST(n)マクロで引数を取得できる.
XSのコードで引数をOUTPUTでそのままかえすと引数自体が変更される.例えば以下のコードは,
void round(arg) double arg CODE: # do something OUTPUT: arg
次のようなCのコードに変換される.ST(0)に記録されていたargを処理したあと,ST(0)戻している.
double arg = (double)SvNV(ST(0)); # do something sv_setnv(ST(0), (double)arg);
Example 5
Arrayを返す関数の例.次のように書くことで,関数の返り値としてArrayを返せる.
void statfs(path) char * path INIT: int i; struct statfs buf; PPCODE: i = statfs(path, &buf); if (i == 0) { XPUSHs(sv_2mortal(newSVnv(buf.f_bavail))); XPUSHs(sv_2mortal(newSVnv(buf.f_bfree))); XPUSHs(sv_2mortal(newSVnv(buf.f_blocks))); XPUSHs(sv_2mortal(newSVnv(buf.f_bsize))); XPUSHs(sv_2mortal(newSVnv(buf.f_ffree))); XPUSHs(sv_2mortal(newSVnv(buf.f_files))); XPUSHs(sv_2mortal(newSVnv(buf.f_type))); } else { XPUSHs(sv_2mortal(newSVnv(errno))); }
MacOSXだとstatfs構造体がどのヘッダファイルに定義されてるかわからなくてコンパイルできなかった.
この例で新しくでてきたこと
- INITディレクティブは引数のデコード処理の直後に実行されるので変数の宣言に丁度良い
- PPCODEディレクティブは返り値の処理を自分で行うときに使う.
- 返り値をArrayで返すために値をスタックにつむにはXPUSHマクロを使う.
- 返り値スタックのSVはmortalになってる.mortalってなに?
- XPUSHマクロは呼び出されるたびにスタックを拡張するので無駄がある.EXTENDマクロで予めスタックを拡張してからPUSHsマクロを使うと効率が良い
Example6
引数にArrayのリファレンスをとって,HashのArrayのリファレンスを返す関数の例.Perlのデータ構造を操作するちょっと複雑な例.(コードは長いので割愛)
この例で新しくでてきたこと
- typemapを使わずにSV *を受けとってSV *を返してる.返してるのはArrayリファレンス一つなのでPPCODEを使う必要がない.
- 引数で受け取った値のValidationをINITでやっている.
if ((!SvROK(paths)) # リファレンスか? || (SvTYPE(SvRV(paths)) != SVt_PVAV) # Arrayのリファレンスか? || ((numpaths = av_len((AV *)SvRV(paths))) < 0)) # 値がはいってるか? { XSRETURN_UNDEF; # undefを返すマクロ }
- Arrayは内部ではAV *で表現される.PerlのArray操作関数と似たマクロが用意されてる.(av_len, av_fetch, av_push など).スタックと同様に事前にArrayを拡張しておくと効率がよくなる.
av_extend(results, numpaths);
- Hashは内部ではHV *で表現される.ArrayといっしょでPerlと似た操作用マクロが用意されている.
- リファレンスはnewRV関数で作る.引数のSV *はAV *やHV *をキャストして渡せる.逆にSvRVでリファレンスからSV *を得た時は自分で適切な型にキャストする必要がある.
Example7 (Coming Soon)
Example8 (Coming Soon)
Example9
ひらいているファイルをXSに渡す方法.
# PerlIOがちゃんと理解できていないのでいろいろあやしい.
stdio.hで定義されてるfputsをPerlで使うには,以下のように書く.
#define PERLIO_NOT_STDIO 0 #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include <stdio.h> int fputs(s, stream) char * s FILE * stream
typemapがうまくやってくれるおかげでPerlIOレイヤをうまくとおるようになってる?
じぶんでPerlIOつかう場合は以下のPerlIO_putsのようにPerlIO用の関数を使う.
typedef PerlIO * OutputStream; int perlioputs(s, stream) char * s OutputStream stream CODE: RETVAL = PerlIO_puts(stream, s); OUTPUT: RETVAL
PerlIOからFILE構造体をとりだして使うこともできる.
int perliofputs(s, stream) char * s OutputStream stream PREINIT: FILE *fp = PerlIO_findFILE(stream); CODE: if (fp != (FILE*) 0) { RETVAL = fputs(s, fp); } else { RETVAL = -1; } OUTPUT: RETVAL
まとめ
- XSまわりの仕組みがどうなっているのか,だいたいわかった
- XSはいろいろと空気を読んでやってくれるので,Cのコードはそんなにかかなくて良い.typemapすごい
- Perl内部系の話(SV, AV, HV とかのはなし)は最低限だけだったので勉強したい