実例で学ぶPHP拡張モジュールの作り方

第2回 Hello, PHP Extension!

この記事を読むのに必要な時間:およそ 8 分

付録A 今回使用したCodeGen_PECLタグ

以下に今回使ったCodeGen_PECLタグの一覧を示します。ほかのタグの説明は今後の連載で使うたびに書きますが,あまり使いそうにないタグは紹介しないかもしれません。すべてを知りたい方はspecファイルのDTDをご参照ください。

表A-1 ルート要素<extension>の属性

属性名省略可説明
name × モジュール名。
version バージョン番号。省略すると警告が出るが,書いても無視される。

表A-2 ルート要素<extension>直下のタグ

タグ名省略可内容または子要素となるタグの説明
summary モジュールの概要。
description モジュールの説明。
maintainers 1つ以上の<maintainer>タグで各メンテナの情報を記述する(<maintainer>の子要素については表A-3を参照⁠⁠。
license ライセンスの種類。BSD,LGPL,PHPのいずれかで,GPLを選ぶとPHP本体のライセンスと矛盾するのでエラー。省略すると⁠unknown⁠になる。
channel PEARパッケージのチャンネル。デフォルト値は⁠pecl.php.net⁠⁠。
release リリース情報(子要素は表A-4を参照⁠⁠。
changelog 更新履歴。過去のバージョンの<release>が子要素になる。
function 関数定義(属性は表A-5,子要素は表A-6を参照⁠⁠。

表A-3 <maintainer>の子要素

タグ名省略可説明
user CVSアカウントなどの短いユーザ名。デフォルト値は⁠unknown⁠⁠。
name フルネーム。デフォルト値は⁠Anonymous Coward⁠⁠。
email メールアドレス。デフォルト値は⁠unknown@example.org⁠⁠。
role 役割。lead,developer,contributor,helperのいずれかで,省略するとunknownになる。PEARパッケージ作成には最低1人の⁠lead⁠が必要。

表A-4 <release>の子要素

タグ名省略可説明
version バージョン番号。ソースコードにも反映される。デフォルト値は⁠0.0.1⁠⁠。
date リリースの日付。省略するとpecl-genを実行した日時になる。
state リリースの状態。stable,beta,alpha,devel,snapshotのいずれか。デフォルト値は⁠devel⁠で,⁠stable⁠以外の場合は安定版でないことを示すEXPERIMENTALファイルが生成される。
notes リリースノート。

表A-5 <function>の属性

属性名省略可説明
name × 関数名。
role public(PHPスクリプトから使える関数)かinternal(モジュール内部の特殊関数)のどちらか。デフォルト値は⁠public⁠⁠。

表A-6 <function>の子要素

タグ名省略可内容または子要素となるタグの説明
summary 関数の概要。
description 関数の説明。
proto 関数のプロトタイプ。戻り値と引数を特定の書式にしたがって記述する(詳しくは次回⁠⁠。
code 関数の実装をC言語(またはC++)で記述する。引数をパースするコードはプロトタイプにしたがって自動生成される。
test テストケース(子要素は表A-7を参照⁠⁠。

表A-7 <test>の子要素

タグ名省略可内容または子要素となるタグの説明
code × テスト内容のPHPコード。
result 期待される出力。デフォルト値は⁠OK⁠⁠。mode属性で結果の評価方法を指定できる(mode属性に指定する値は表A-8を参照⁠⁠。

表A-8 <result>タグのmode属性

結果の評価方法
plain 内容が出力と等しいかを調べる。
format 内容はフォーマット文字列として扱われ,出力がフォーマットにマッチするか調べる。フォーマットはsscanf()関数のような書式だが,正規表現に変換して評価される。/ はエスケープしなくてよい。
regex 内容は正規表現として扱われ,出力が正規表現にマッチするか調べる。正規表現中の / はエスケープしないといけない。

付録B 今回使用したZend API

Zend APIには,今回使ったphp_printf()のほかに,文字列やバイナリデータをそのまま出力するための関数php_body_write()とphp_header_write()があります。両者の違いはPHPの出力バッファリングの影響を受けるか否かですが,通常はphp_body_write()を使います。いずれも戻り値は出力されたデータの長さ(バイト数)です。

表B-1 出力用関数

定義説明
int php_printf(const char *format, ...) フォーマットに従って文字列を出力する(バッファリングあり⁠⁠。
フォーマットの書式と対応する引数の型はprintf(3)とほぼ同じ※4⁠。
int php_body_write(const char *str, uint str_length TSRMLS_DC) 長さを指定してデータを出力する(バッファリングあり⁠⁠。
int php_header_write(const char *str, uint str_length TSRMLS_DC) 長さを指定してデータを出力する(バッファリングなし⁠⁠。

また,php_body_write()およびphp_header_write()のラッパーとして表B-2,表B-3のようなマクロも用意されています。

表B-2 出力用マクロ(バッファリングあり)

定義説明
int PHPWRITE(const char *str, uint str_length) 長さを指定してデータを出力する。
PUTS(const char *str) ヌル終端文字列を出力する。
do-whileブロックとして定義されており,戻り値を得ることはできない。
char PUTC(char c) 1文字出力する。cは変数でないといけない。戻り値はc。

表B-3 出力用マクロ(バッファリングなし)

定義説明
int PHPWRITE_H(const char *str, uint str_length) 長さを指定してデータを出力する。
PUTS_H(const char *str) ヌル終端文字列を出力する。
do-whileブロックとして定義されており,戻り値を得ることはできない。
char PUTC_H(char c) 1文字出力する。cは変数でないといけない。戻り値はc。
※4
Apacheに由来する独自の実装です。PHP6ではUnicode文字列(UChar *)をunicode.runtime_encodingに従ってC文字列(char *)に変換して出力する⁠r⁠指示子、zstr共用体をUnicode文字列として扱うかC文字列として扱うかを指定して出力する⁠R⁠指示子、zstr共用体をunicode.semanticsがOnならUnicode文字列として、OffならC文字列として扱う⁠v⁠指示子、Unicode文字列をC文字列に変換する際のエンコーディング(UConverter *)を指定する⁠#⁠修飾子といった独自の拡張もあります。

付録C TSRM(Thread Safe Resource Manager)について

付録BでTSRMLS_DCというマクロが出てきました。Zend APIにはTSRMというスレッドセーフにメモリなどのリソースを管理するためのリソースマネージャがあり,TSRMLS_DCはTSRMのスレッドローカルストレージを受け渡しするためのマクロです。TSRMLS_DCとは⁠Thread Safe Resource Manager Local Storage, Declaration Comma⁠の頭文字をとったもので,TRSMLS_CC(Thread Safe Resource Manager Local Storage, Call Comma)と対になってそれぞれ定義側,呼び出し側でほかの引数に続けて,区切りのコンマ無しで記述されます。コンマを必要としないのは名前が示すように,コンマもマクロに含まれるからです。ほかに引数をとらない関数の定義および呼び出しにはそれぞれTSRMLS_DとTSRMLS_Cが使われます。

Windows版以外のPHPはマルチスレッド非対応でコンパイルされることが多く,このときTSRM系マクロの定義は空(TSRMLS_Dはvoid)となっており,書き忘れてもコンパイルでき,正常に動作しますが,マルチスレッド対応(ZTS,Zend Thread Safe)モードではコンパイルできません。先に紹介したPHPWRITE()のように,よく使うAPIがTSRMを意識せずに使えるようになっているのは,単に記述を短くするためだけでなく,このような不具合を予防する目的があってのことだと考えられます。

著者プロフィール

関山隆介(せきやまりゅうすけ)

ジンガジャパン株式会社に所属。PHP拡張モジュールを作った数なら(たぶん)日本一。PHP 5.3に対応したものはPEARチャンネルGitHubで公開中。

URLhttp://d.hatena.ne.jp/rsky/