PerlQt移植ヒストリ(第2回)
付属のチュートリアルを使用して、ビルドしたてのPerlQtを早速実行して見た。
$ perl -W -Iblib/arch -Iblib/lib -I$QTDIR/lib -Itutorials-2.0/t1 \ tutorials-2.0/t1/t1 Can't locate object method "new" via package "Qt::Application" (perhaps you forgot to load "Qt::Application"?) at tutorials-2.0/t1/t1 line 3.
「ひょっとしたら、"Qt::Application" をロードし忘れた?」と言われても困る。そのようなPMファイルは存在しないのだから。
デバッグコードを挿入して原因を調べたところ、以下の関数に問題があることを発見した。
void pig_autoload_methods(const char *pig0, pig_classinfo *pig1) { char *pigmethod; if(!_pig_autoloaded_methods) _pig_autoloaded_methods = newHV(); hv_store(_pig_autoloaded_methods, (char *)pig0, strlen(pig0), newSViv((IV)pig1), 0); pigmethod = new char [strlen(pig0) + 11]; sprintf(pigmethod, "%s::AUTOLOAD", pig0); newXS((char *)pigmethod, (XS((*)))PIG_AUTOLOAD, (char *)__FILE__); delete [] pigmethod; }
この関数は各パッケージのAUTOLOADサブルーチンを定義するものだ。
しかし、肝心のパッケージを作成している処理が見つからない。
これではPerlに「ロードし忘れた?」と言われても仕方が無い。
色々と調べて見たが、パッケージを作成できるC言語の関数は見つからなかった。
そ こで、Perlスクリプトによってパッケージを作成する以下の方法を考えた。
void pig_autoload_methods(const char *pig0, pig_classinfo *pig1) { char *pigmethod; if(!_pig_autoloaded_methods) _pig_autoloaded_methods = newHV(); hv_store(_pig_autoloaded_methods, (char *)pig0, strlen(pig0), newSViv((IV)pig1), 0); pigmethod = new char [strlen(pig0) + 11]; #if 0 /* 以下を差し替え */ sprintf(pigmethod, "%s::AUTOLOAD", pig0); #else sprintf(pigmethod, "%s::autoload", pig0); static char def_pkg[] = "\ package %s; \ sub AUTOLOAD \ { \ return undef if ($AUTOLOAD eq \"%s\"); \ &%s; \ } \ 1; \ "; int bytes = sizeof (def_pkg) + strlen(pig0) + strlen(pigmethod) * 2; char *pkg = new char [bytes]; sprintf(pkg, def_pkg, pig0, pigmethod, pigmethod); perl_eval_pv(pkg, 1); delete [] pkg; #endif newXS((char *)pigmethod, (XS((*)))PIG_AUTOLOAD, (char *)__FILE__); delete [] pigmethod; }
上記の修正により、Perlに「ロードし忘れた?」と言われずに済んだ。
ただし、表示される画面はチュートリアルもサンプルも以下のようにZaurusらしくない。
ここまで来るのに、実際には前回から1週間分の通勤時間と休日の数時間を必要とした。しかし、まだまだ先は長そうだ。
ここで培ったノウハウを以下に記載した。
XSからパッケージを作成する
多くのPerlパッケージはPerlスクリプト(PMファイル)で作成される。
h2xsコマンドで作成されるXSの初期ビルド環境でも1つのPMファイルが用意され、そのPMファイルがpackage命令によってパッケージを作成する。
単一のパッケージを作成する場合はこの方法で完結するのだが、1つのパッケージに多数のサブパッケージが付随する場合、それらのサブパッケージごとにXSのビルド環境を用意するよりも、XSから動的にサブパッケージを作成した方が効率的かつ高速で、管理も楽である。
以下の例では、Pkg1::initサブルーチンを呼び出ことにより、Pkg1::Sub1パッケージを生成し、次にPkg1::Sub1::helloを呼び出すことによってAUTOLOADの仕組み(呼び出されたサブルーチンがないときに同じパッケージのAUTOLOADサブルーチンが呼び出される仕組み)が働き、Pkg1::Sub1::helloサブルーチンのエントリが動的に作成される。
/* h2xs -n Pkg1 */ #include "EXTERN.h" #include "perl.h" #include "XSUB.h" /* * h2xsコマンドが生成する部分 * : * : */ XS(XS_Pkg1_Sub1_hello) { dXSARGS; printf("hello world\n"); XSRETURN_EMPTY; } XS(XS_Pkg1_Sub1_autoload) { dXSARGS; int i; for (i = 0; i < items; i++) { printf("ST[%d]:%s\n", i, SvPV_nolen(ST(i))); } newXS("Pkg1::Sub1::hello", XS_Pkg1_Sub1_hello, __FILE__); XSRETURN_EMPTY; } MODULE = Pkg1 PACKAGE = Pkg1 /* * h2xsコマンドが生成する部分 * : * : */ void init() CODE: perl_eval_pv("\ package Pkg1::Sub1; \ sub AUTOLOAD \ { \ Pkg1::Sub1::autoload($AUTOLOAD); \ goto &$AUTOLOAD; \ } \ 1; \ ", 1); newXS("Pkg1::Sub1::autoload", XS_Pkg1_Sub1_autoload, __FILE__);
上記のXSをビルドして、以下のスクリプトを実行する。
#!/usr/bin/perl -Iblib/arch -Iblib/lib use Pkg1; Pkg1::init(); Pkg1::Sub1::hello(); Pkg1::Sub1::hello();
出力結果は以下の通り。
ST[0]:Pkg1::Sub1::hello hello world hello world
2回目に呼び出したPkg1::Sub1::helloサブルーチンのエントリが作成済みであることが分かる。
上記のテクニックはC++のクラスを簡単にパッケージとして表現する手段として利用できるはずである。