CodeGen_PECL (かつては PECL_Gen という名前でした) は、シンプルな XML 形式のスペックファイルから PHP エクステンションの基本的な枠組みを自動的に作成するツールです。 実際の機能は pecl-gen というスクリプトで提供されており、 このスクリプトは CodeGen_PECL パッケージがインストールします。
また、よりシンプルな (より低機能な) プロトタイプファイルもサポートしています。 これは ext_skel というシェルスクリプトで使用する形式であり、 このスクリプトは PHP のソースコードに同梱されています。
pecl-gen は、これまでの ext_skel とは異なって完全に PHP 5 だけで作成されています。 awk や sed といったその他のツールは不要です。使っているのは PHP の関数と PEAR モジュールだけであり、PEAR モジュールについても デフォルトのインストールで常に有効となるものしか用いていません。 つまり、PHP が実行できる環境ならどこでも使用できることになります。
PHP 5 が必要となるのは、XML スペックファイルから C/C++ コードへの変換部だけです。 CodeGen_Pecl で作成したコードは、手続き型の機能のみを使用するのであれば PHP 5 だけではなく PHP 4 の API でも動作するようになっています。 オブジェクト指向の機能は PHP 5 にのみ対応しています。 PHP 4 と PHP 5 の間にはオブジェクト指向の API に大きな違いがあるので、両方に対応させるのは難しいのです。
CodeGen_PECL は、エクステンションを書く際に必要となる機能を可能な限りサポートしています。 現時点では、以下の項目についてのコードとドキュメントを作成することができます。
関数
定数
php.ini の設定ディレクティブ
リソース型
スレッド単位のグローバル変数
テストケース
CodeGen_PECL は、それ以外に Unix 環境でのビルド用の config.m4 や Windows 用の VisualStudio *.dsp プロジェクトファイル、 そして PEAR/PECL パッケージの作成用の package.xml ファイルも作成します。 Windows Scripting Host を使用した新しいビルドシステムについては、 対応するために現在作業中です。現状でも一応 config.w32 は作成されるのですが、単純なセットアップしか動作しません。
DocBook XML で書かれたドキュメントのテンプレートも作成されます。 これは単体でコンパイルすることもできますし、PHP のマニュアルに組み込むこともできます。PHP マニュアルに組み込みたければ、 できあがったディレクトリを単に PHP マニュアルのソースにコピーするだけです。
テストケースについては、生成される関数ごとにひとつずつ作成されます。 追加のテストケースを指定することもできます。
CodeGen_PECL は PEAR (PHP Extension and Application Repository) http://pear.php.net/CodeGen_PECL から取得できます。
CodeGen_PECL のインストールは、 PEAR インストーラを使ってオンラインで行うのが一番簡単です。 以下のようなコマンドを実行します。
pear install --alldeps CodeGen_PECL
|
PEAR インストーラは、依存するパッケージも含めて自動的にダウンロードとインストールを行います。
pear.php.net からパッケージをダウンロードしてインストールする場合は、 依存性の解決を自分でする必要があります。現時点では、 CodeGen_PECL はふたつの PEAR パッケージに依存しています。 ひとつは Console_Getopt で、 これは PEAR をインストールしたら自動的についてくるものです。 もうひとつは CodeGen で、 これはコードジェネレータの基本パッケージとなります。 つまり、このパッケージをインストールするには CodeGen と CodeGen_PECL をダウンロードすることになります。 実際にインストールをするときには PEAR インストーラを次のように使用します。
pear install CogeGen-1.0.1.tgz
pear install CogeGen_PECL-1.0.3.tgz
|
![]() | 訳注: このセクションの内容は少し古くなっています。 現在、CodeGen_PECL のソースは PEAR の CVS から離脱しており、 自前の SVN リポジトリ で公開されています。 |
CodeGen_PECL の開発版を PEAR の CVS からインストールすることもできます。 CVS のスナップショットには、リリース版にはまだ含まれていない機能が搭載されていることもあります。 ただ、リリース版のパッケージに比べてテストが不十分です (って言うか、きちんと動作すらしないかもしれません)。 どうしても最新版の CodeGen_PECL のスナップショットを使用したい場合は、 PEAR の CVS からファイルをチェックアウトしてから以下のコマンドを実行します。
cd pear
cd CodeGen
cvs update
pear install --force package.xml
cd ..
cd CodeGen_PECL
cvs update
pear install --force package.xml
cd ..
|
pecl-gen には 3 通りの使用法があります。 デフォルトのモードは、XML の定義ファイル (次の章で説明します) にもとづいて、すぐにコンパイルできるエクステンションの雛形を生成します。 ext_skel 互換モードは、 コマンドラインのパラメータや (オプションの) 関数プロトタイプファイルから エクステンションを生成します。 即時モード (immediate mode) は、関数のプロトタイプをコマンドラインから受け取り、 その関数だけの C コードの雛形を標準出力に書き出します。
ext_skel 互換モードと即時モードについては このドキュメントでは説明しません。 ext_skel のドキュメントを参照ください。
pecl-gen を用いて エクステンションの雛形作成からコンパイル、テスト、インストールまでの手順を ひとつひとつ進めていく解説が、このドキュメントの後半の "使用法" の章にあります。
pecl-gen --help の出力のハードコピーを以下に示します。
pecl-gen 1.0.3, Copyright (c) 2003-2005 Hartmut Holzgraefe
Usage:
pecl-gen [-h] [--force] [--experimental] [--version]
[--extname=name] [--proto=file] [--skel=dir] [--stubs=file]
[--no-help] [--xml[=file]] [--full-xml] [--function=proto] [specfile.xml]
-h|--help this message
-f|--force overwrite existing directories
-d|--dir output directory (defaults to extension name)
-l|--lint check syntax only, don't create output
--linespecs generate #line specs
-x|--experimental deprecated
--function create a function skeleton from a proto right away
--version show version info
the following options are inherited from ext_skel:
--extname=module module is the name of your extension
--proto=file file contains prototypes of functions to create
--xml generate xml documentation to be added to phpdoc-cvs
these wait for functionality to be implemented and are ignored for now ...
--stubs=file generate only function stubs in file
--no-help don't try to be nice and create comments in the code
and helper functions to test if the module compiled
these are accepted for backwards compatibility reasons but not used ...
--full-xml generate xml documentation for a self-contained extension
(this was also a no-op in ext_skel)
--skel=dir path to the skeleton directory
(skeleton stuff is now self-contained)
|
エクステンションの定義ファイルの最上位に位置するのが <extension> タグです。 エクステンションの名前を name=... 属性で指定します。この名前は、C 言語で使用できる形式でなければなりません。 というのも、この名前はディレクトリ名となるだけではなく 生成される C 言語のコード中でシンボル名の一部としても用いられることになるからです。
対象となる CodeGen_PECL のバージョンを、version=... 属性で指定することもできます。インストールされている CodeGen_PECL のバージョンがここで指定したバージョンより古い場合は、 pecl-gen コマンドはは何も処理を行いません。 逆に、インストールされている CodeGen_PECL のほうが新しい場合は、 指定されているバージョンとの間で互換性を失う変更があった場合も考慮して 古いバージョンの挙動を再現するようにします。
![]() | 今までのところ、このような互換性を失う変更は 2 回発生しています。 バージョン 0.9.0 から登場した新しいプロトタイプパーサは、 それより前のバージョンとの互換性を失っている箇所があります。 また、バージョン 1.0.0 からは生成されるコード中の変数名が一部変更されました。 |
<summary> と <description> は、 定義ファイルの先頭に記述しなければなりません。 summary はそのエクステンションについての簡単な (1 行程度の) 説明です。description には、詳細な説明を好きなだけ記述します。 これらは、後に package.xml ファイルやドキュメントの雛形を生成する際に用いられます。 summary の内容は、 phpinfo() の実行結果の中にも表示されるようになります。
Example 1. 基本的なエクステンションの例
<?xml version="1.0" ?>
<!DOCTYPE extension SYSTEM "../extension.dtd">
<extension name="sample" version="0.9.0">
<summary>A sample PHP extension</summary>
<description>
This is a sample extension specification
showing how to use CodeGen_PECL for
extension generation.
</description>
...
|
<group> タグは複数の要素をグループにまとめるもので、 トップレベルの <extension> タグの配下で使用できます。 <group> タグはネストさせることもでき、 また任意の属性を指定することができます。
group の内部の要素は、 そのタグがサポートする属性 (で、そのタグでは指定されていないもの) のデフォルト値をグループのスタックに問い合わせることがあります。 また、ネストされている各段階のグループの値の積算値を問い合わせることもあります。
現在、group の値を見て処理を行っているのは <class>, <constant>, <function>, <global>, <interface>, <phpini>, そして <resource> といったタグの中の 'if' 属性だけです。
エクステンションのリリース情報に含めなければならない情報は、 作者名とメンテナ名、バージョン番号、リリース状態 (stable, beta, ...)、 リリース日、ライセンス、そして過去のリリースからの変更内容などです。 phpinfo() の出力時にそのエクステンション用に使用する画像ファイルを 指定することもできます。
<maintainers> や <release>、 <changelog> といったタグに記述する内容は、PEAR の package.xml と同じです。 PEAR のドキュメント を参照ください。
pear.php.net ではなく独自のチャネルサーバでエクステンションを公開したい場合は、 <channel> タグを追加してそこでチャネルサーバを指定します。
Example 2. リリース情報
...
<maintainers>
<maintainer>
<user>hholzgra</user>
<name>Hartmut Holzgraefe</name>
<email>hartmut@php.net</email>
<role>lead</role>
</maintainer>
</maintainers>
<license>PHP</license>
<channel>pear.php-baustelle.de</channel>
<release>
<version>1.0</version>
<date>2002-07-09</date>
<state>stable</state>
<notes>
The sample extension is now stable
</notes>
</release>
<changelog>
<release>
<version>0.5</version>
<date>2002-07-05</date>
<state>beta</state>
<notes>First beta version</notes>
<release>
<release>
<version>0.1</version>
<date>2002-07-01</date>
<state>alpha</state>
<notes>First alpha version</notes>
<release>
</changelog>
...
|
<license> タグには、 package.xml の場合よりもより厳しい制約があります。 というのも、これをもとにして実際の LICENSE ファイルを生成することになるからです。 現在ここに指定できるライセンスは PHP、BSD あるいは LGPL のいずれかのみです。それ以外は 'unknown' となります。
![]() | ここで、GPL を選択することはできません。というのも GPL と PHP ライセンスには互換性がないからです。この件についての詳細は FSF のライセンス比較ページ を参照ください。 |
phpinfo() の出力時に使用するロゴを指定するには <logo> タグを使用します。 実際の画像データは、src=... 属性で指定したファイルから読み込みます。 あるいは、base64 エンコードしたものを <logo> タグの内部にインラインで埋め込んでもかまいません。 ロゴの MIME タイプは mimetype=... 属性で指定します。GIF、PNG および JPEG 形式の画像については、MIME タイプを自動的に検出します。
Example 5. ロゴ画像のインライン指定
...
<logo>
<![CDATA[
R0lGODdhFQASAOMAAMDEwJCQkLCwsGhoaFhcWLjAuDA0MPj8+FBUUPj4+AAA
AAAAAAAAAAAAAAAAAAAAACwAAAAAFQASAAAEehDISWsNQIhBuv+DphWhABhH
qq5GpgHluc5HK24vmiKGwfeIgdAU0wkSyCQSkAhgQhgdQUlNCAKkq0BaVWqh
29S0i8QRtFyyNXQOhA9jshktVq8F7Xe8O3en5WxXTgEcdn2DAwF2hHiCGoQc
HASSQgRukwiZmpucmwYRADs=
]]>
</logo>
...
|
依存性は <deps> で指定します。 <deps> セクション自体では、 プログラミング言語 (language=... 属性) や対象プラットフォーム (platform=... 属性) を設定することができます。
対応している言語は、C (lang="c") と C++ (lang="cpp") です。 ここで指定した言語によって生成されるコードが変わるわけではありません (pecl-gen は常に C のコードを生成します。 というのも PHP エクステンションの API がピュア C で書かれているからです) が、エクステンションをコンパイルしたりリンクしたりするときの方法が変わります。 C++ を選択するのは、C++ で書かれた外部ライブラリを使用するときだけにしましょう。
![]() | Delphi をサポートすることも考え続けてはいます。 ただ、それはかなり大掛かりな作業になりそうです (コードの生成をテンプレートベースにしなければならないでしょう)。 よっぽどその作業がやりたくなったであるとか 誰かが資金を援助してくれるようになったであるとかの事情がない限り、 今のところ作業に取り掛かるつもりはありません。 |
プラットフォームとしてサポートしているのは、Unix 系システム (platform="unix")、Microsoft Windows (platform="win32") あるいはその両方 (platform="all") のいずれかです。
<deps> セクションの中では <with>、<lib> そして <header> というタグを使用できます。 これらを用いて、configure スイッチやライブラリ、 そしてヘッダファイルの依存性を定義します。
Unix 系のシステム、あるいは Windows の Cygwin 環境でエクステンションをビルドする際には、 必要な外部ライブラリやヘッダファイルがそのシステムに存在するかどうかを configure スクリプトが探します。 --with-... オプションを使用すると、 ライブラリやヘッダを実際にどこから探すのかを指定できるようになります。 それらのファイルがデフォルトの場所以外にインストールされているときなどに使用します。 あるいは、複数のバージョンがインストールされているときに 特定のバージョンを指定したい場合にも使用します。
<with> タグには 3 つの属性を指定します。 name=... は --with-... オプションで使用する名前、testfile は configure スクリプトの実行時に調べたいファイルへの相対パス、 そして --with-... オプションの引数を省略した場合にファイルを探すパスのリストを defaults に指定します。
name と defaults を省略した場合は、それぞれエクステンション名と /usr:/usr/local が自動的に設定されます。 testfile 属性は必須です。
<with> の内部に記述したテキストは、 configure --help の出力結果の中で そのオプションの解説として表示されます。
そのエクステンションに必要なヘッダファイルを指定するには <header> を使用します。 ここで指定したすべてのヘッダは、 コンパイル用に設定したインクルードパスに存在する必要があります (先ほどの --with のセクションも参照ください)。 ここで指定したヘッダに対応する #include ステートメントは、自動生成されるコードの中で一番最後に追加されます。 もしその他の #include より前に置きたい場合は、prepend="yes" 属性を指定してください。
デフォルトでは、<with> で指定したパスのサブディレクトリ include からヘッダファイルを探します。 それ以外の相対パスを指定する必要がある場合は、 path 属性を指定します。
必要な外部ライブラリを指定するのは <lib> タグです。 name=... 属性は必須で、ここにライブラリ名を指定します。 名前としてたとえば "sample" を指定したときに実際に探すファイルは、 Unix 系システムの場合はスタティックライブラリ libsample.a あるいはダイナミックライブラリ libsample.so、 Windows の場合は sample.DLL となります。
そのライブラリがエクスポートしているであろう関数シンボル名を function=... 属性で指定することもできます。 エクステンションの configure を実行するときに、この関数シンボルを探します。 こうすることで、正しいバージョンのライブラリが見つかったかどうかを確実に調べることができます。 Windows の VisualStudio ではこのチェックを行うことができません。 この場合はライブラリをただプロジェクトファイルに追加するだけです。
PHP 5.1 以降では、複数エクステンション間での依存性を定義することができます。 この依存性にもとづいて、エクステンションの初期化やシャットダウンの順番が決まります。 たとえば、pdo が存在する場合にのみ pdo_mysql をロードするという設定や、 互いに衝突するエクステンションを同時にロードしないようにするなどといった設定を行います。
この依存性を定義するには、<deps> の内部で <extension> を使用します。 <extension> には少なくとも name=... を指定する必要があります。
デフォルトの意味は、 「いま作成しているエクステンションを使用するには、 ここで指定した名前のエクステンションが事前に初期化されていなければならない」 です。この設定を明示的に指定するには、 type=... 属性を REQUIRED にします。 それ以外に使用できる値は OPTIONAL と CONFLICTS です。 OPTIONAL は、 「事前に初期化されている必要があるが、 なくてもかまわない」という意味を表します。 CONFLICTS は、 「そのエクステンションと同時に使用することはできない」 という意味を表します。
エクステンションの API と CodeGen_PECL は、どちらも依存性チェックにバージョン指定を含めることができます。 しかし、実際のチェックは PHP 5.1 の時点ではまだ実装されていません。 CodeGen_PECL ではすでにこれらをサポートしており、 PHP 側でこの機能が実装されればすぐにそれを使用できるようになります。
バージョンの依存性を定義するには、 version=... 属性とオプションの rel=... 属性を使用します。 version に指定するのは、'PHP 標準形式' のバージョン番号文字列 (http://php.net/version_compare を参照ください) で、rel に指定するのは 実際のバージョンと version との比較に使用する演算子です。 rel のデフォルトは >= です。これは、少なくとも指定したバージョン以降が必要という意味になります。 >= の代わりに指定できるのは、 >= や >、 =、< そして <= です。また、これらをテキストで表した形式 ge、gt、 eq、lt および le も使用できます。
エクステンションのソースファイル用のカスタムコードを追加するには <code> タグを使用します。role=... と position=... で、 そのコードを実際のどのソースファイルのどの部分に追加するのかを指定します。
role に使用できるのは、'code' (デフォルト) か 'header' のいずれかです。 'code' は C や C++ のコードファイル、 'header' はヘッダファイルを表します。 position に指定できるのは 'top' か 'bottom' (デフォルト) のいずれかで、 それぞれ生成されるファイルの先頭付近、末尾付近を意味します。
if=... 属性を使用すると、 条件付きコンパイルを指定することができます。この属性の引数には、 "#if" ディレクティブで使用できるような C プリプロセッサの式を指定します。
特定のライブラリが使えるときにのみ関数をコンパイルして登録したいという場合は、 単純に HAVE_... マクロを使うこともできます。 この設定は config.h に書き込まれます。
Example 11. 条件付きコンパイル
この関数は、configure が FOO あるいは BAR を検出したときにだけ使用可能となります。
<function name="foobar" if="HAVE_FOO || HAVE_BAR">
...
</function>
|
つまり、この関数用に生成されるコードはすべて 次のような条件ブロックの中に書き込まれることになります。
#if HAVE_FOO || HAVE_BAR
...
#endif
|
今のところ、この機能は手続き型の関数にしか使用できません。 しかし、近い将来にそれ以外のコードにも適用できるようになる予定です。
PHP の関数宣言には、 name=... 属性のほかに少なくとも <proto> タグを設定する必要があります。
関数名には、C の名前として有効な形式のものならなんでも指定できます。 しかし、PHP のコーディング規約では、 エクステンションが提供する関数の名前の先頭には そのエクステンションの名前をつけておく必要があります。
<proto> タグで指定した関数プロトタイプから、 返り値の型や関数名、そして引数リストを取得します。 プロトタイプの中の関数名は、 <function> で指定した name 属性と一致しなければなりません。
引数や返り値の型としては、次のいずれかを使用することができます。
bool
int
float
string
array
object
mixed
callback
resource [typename]
stream
関数のドキュメント用に、1 行の説明を書いた <summary> タグと詳細な説明を書いた <description> タグが必要です。 それぞれの内容が、その関数のドキュメント用に生成した DocBook XML ファイルにコピーされます。 <description> の中では DocBook のタグを使用することもできます。 しかし、pecl-gen ではこのタグを認識しますが、 妥当性の検証用の XML パーサに定義ファイルを食わせると文句を言われるかもしれません。
その関数用の <code> が指定されていない場合は、 引数のパースと結果を返すためのコードの雛形を自動生成します。 <code> セクションの内容は、 引数のパース用コードの直後に挿入されます。 返り値の設定は、挿入するコードの中で記述します。
関数のパラメータや返り値の型は、関数の <proto> タグの中で指定します。 <proto> タグの書式は、PHP のソースコードの proto 行と同じです。
パラメータ名の先頭には $ をつけない
関数のパラメータと返り値には型を指定する
オプションのパラメータは角括弧で囲む
オプションのパラメータにはデフォルト値を指定することもできる
引数の数が可変の関数は、パラメータリストの最後に ... を追加することで定義する
Example 12. 関数プロトタイプの例
// パラメータも返り値もなし
void funcname(void);
// int 型のパラメータをひとつ受け取り、int 型の値を返す
int funcname(int param);
// 必須パラメータをひとつ、そして任意のパラメータをふたつ
// 受け取り、文字列を返す
string funcname(string param1 [[, string param2], string param3]);
// デフォルト値を指定したオプションパラメータ
int funcname([int param = 42]);
// 可変引数
int funcname(string param1, ...);
// オブジェクトやリソースも利用可能
object foo funcname(resource bar param1);
|
string パラメータ用には、ふたつのローカル変数が作成されます。 実際の文字列データをあらわす char* 変数と、 文字列の長さをあらわす int 変数です。
char * の名前はパラメータ名と同じ、 一方 int のほうはパラメータ名の後に _len をつけた名前となります。
array パラメータ用には、ふたつのローカル変数が作成されます。 パラメータ名と同じ名前の zval * 変数と、 パラメータ名の後に _hash を追加した名前の HashTable * 変数です。
配列パラメータを処理するには、Zend API の zend_hash_num_elements や zend_hash_internal_pointer_reset、そして zend_hash_get_current_data_ex といった関数を使用する必要があります。
Zend の HashTable 型や zend_hash_... 関数の内容については、 このドキュメントでは説明しません。
PHP のオブジェクト用には、 zval * 型のローカル変数が作成されます。 これは、作成される実際のオブジェクトを指すポインタとなります。 これらの具体的な扱い方については、 このドキュメントでは説明しません。
resource パラメータ用には、 3 つのローカル変数が作成されます。
リソースの中身を指すポインタ。パラメータと同じ名前。
リソース変数そのものへの zval * ポインタ。 パラメータ名の後に _res を付加した名前。 これは FREE_RESOURCE() マクロでリソースを開放する際に必要となる。
リソース ID を表す数値を格納する int 変数。 パラメータ名の後に _resid を付加した名前。
mixed パラメータにはさまざまな型が渡される可能性があります。 そのため、汎用的な zval * 型のローカル変数のみを用意します。 Zend API で zval を操作する方法については、 このドキュメントでは説明しません。
プロトタイプのパラメータリストのの最後には、 オプションパラメータを指定することができます。 オプションパラメータは、各括弧で囲まなければなりません。 bool、 int、float そして string といった型の場合は、デフォルト値を指定することもできます。
Example 15. オプションパラメータ
// 単純なオプションパラメータ
int f1([int p1])
// デフォルト値を指定したオプションパラメータ
int f2([int p1 = 23])
// 必須パラメータと複数のオプションパラメータ
int f3(int p1 [, int p2 [, int p3]])
|
将来のバージョンでは、 次のように状況に依存するオプションパラメータもサポートする予定です。
この関数にはパラメータを 1 つ指定することもできますし 3 つあるいは 4 つ指定することもできますが、 2 つ指定することはできません。 しかし、現時点ではまだこの機能は実装されていません。可変引数の関数 (PHP の max() や printf みたいなもの) を宣言することもできます。 その場合は、パラメータリストの最後に '...' を追加するか、あるいはパラメータリストに '...' だけを指定します。 '...' の前には、bool か int、float、string そして mixed のいずれかの型を指定することもできます。 型を省略した場合のデフォルトは mixed です。
可変引数の個数は、ローカル変数 varargc に格納されます。 実際の値は varargv という配列に格納されます。 各パラメータの値は、上の 'パラメータの型' で説明した C のデータ型を用いて格納します。 string 型については、別の配列 varargv_len を別途作成し、 そこで文字列の長さを保持します。
Example 16. 可変引数
<function name="f_vararg1">
<proto>void f_vararg1(...)</proto>
<code>...</code>
</function>
<function name="f_vararg2">
<proto>void f_vararg2(string format, ...)</proto>
<code>...</code>
</function>
<function name="f_vararg3">
<proto>int f_vararg3(int...)</proto>
<code>
int i;
long sum = 0;
for (i = 0; i < varargc; i++) {
sum += varargv[i];
}
RETURN_LONG(sum);
</code>
</function>
|
その他の例は、docs/examples ディレクトリにある varargs.xml を参照ください。
Boolean 型の値を返す際には、 RETURN_BOOL(b)、 RETURN_TRUE および RETURN_FALSE のいずれかのマクロを使用します。
Integer 型の値を返すには、 RETURN_LONG(l) マクロを使用します。
Float 型の値を返すには、 RETURN_DOUBLE(d) マクロを使用します。
Strings 型の値を返すには、 RETURN_STRING(str, duplicate)、 RETURN_STRINGL(str, len, duplicate) あるいは RETURN_EMPTY_STRING() のいずれかのマクロを使用します。
RETURN_STRING() ではゼロ終端の文字列のみを使用できます。 RETURN_STRINGL を使用すると、途中にヌルバイトを含むようなバイナリ文字列も返せるようになります。 どちらのマクロにも duplicate というパラメータがあります。これは、 文字列をコピーして返すのかどうかを指定します。 emalloc() や estrdup() を用いて自分で str を確保した場合を除き、 このパラメータは常に 1 を指定することになるでしょう。
resource 型の値のためには、 特別なローカル変数 return_res を作成します。これは、リソースの中身へのポインタとなります。 中身のデータを自分で確保しなければならないのか、 あるいは return_res の指す先が事前に確保されていてそこに値を格納するだけでいいのか、 それはリソースの alloc 属性によって決まります。
返り値のリソース用の確保や初期化に失敗した場合は、 エラーを表すために RETURN_FALSE あるいは RETURN_NULL を使用することになるでしょう。
array 型の値を返すには、値を return_value 変数に追加します。 これは、空の配列として初期化済みです。
配列 return_value に値を追加するには、Zend API の add_assoc_...() や add_index_...()、そして add_index_next_...() を使用します。
...
内部関数は、PHP エクステンション API からコールされるものです。 エクステンションの初期化や後始末、 phpinfo() 用の情報の取得などを行います。
内部関数を定義するには、 role="internal" 属性および name=... 属性を設定しなければなりません。 name に指定できるのは、以下のいずれかのみです。
モジュールの初期化用関数です。これは、 PHP のサーバモジュールあるいはスタンドアロンバイナリ (CGI あるいは CLI) の開始時に一度だけコールされます。
モジュールの後処理用関数です。これは、 PHP のサーバモジュールあるいはスタンドアロンバイナリ が正常終了した際に一度だけコールされます。 プログラムがクラッシュしたり、 その他致命的なエラーで終了したりした場合はコールされません。
リクエストの初期化用関数です。これは、 PHP サーバモジュールが実際に PHP スクリプトを実行する直前、 あるいはスタンドアロンバイナリ (CGI あるいは CLI) が MINIT() を実行した後にコールされます。
リクエストの後処理用関数です。 これは、PHP サーバモジュールが PHP コードの実行を終えたり中断したりした後にコールされます。 PHP の致命的なエラーが発生した場合でもコールされますが、 致命的なエラーや C 言語レベルのクラッシュが発生した場合に これがコールされることを期待してはいけません。
このエクステンション用の phpinfo() ハンドラです。phpinfo() を実行したとき、あるいはスタンドアロンの PHP バイナリをコマンドラインオプション -i つきで実行したときにコールされます。
<code> セクションを指定しなかったときに自動生成されるコードに含まれる内容は、 エクステンションの名前と 1 行の説明、 バージョンとリリース日、 (指定した場合は) ロゴ画像、そしてすべての php.ini ディレクティブの グローバル値/実際の値 です。
内部関数用の <code> セクションは、C 言語の関数の本体と同じように書きます。 ローカル変数の定義もここで行います。
PHP の定数は <constant> タグで定義します。
定数の名前や型、そして値はそれぞれ name=...、type=... そして value=... で指定します。 定数名は、C 言語の名前として使用できる形式でなければなりません。 PHP の定数名は、規約上は大文字のみを使用することになっています (が、必須ではありません)。型として使用できるのは string、int そして float です。値はその型に依存します。 int および float では、 数値形式の文字列あるいは C の定数名 (ANSI C/C++ で定義されている定数、あるいは C のプリプロセッサで #define した定数) を使用できます。 string の値については常に "as is" で用いられるので、定数を使用することはできません。
define=... 属性を yes にすると、PHP の定数だけでなく 同名、同じ値の C 言語の定数もヘッダで #define します。 これで、生成される C 言語のソースでも同じ名前の定数を使用できるようになります。
一方、すでに C 言語レベルで整数の定数が定義されている場合は、 name を指定するだけで 同じ定数を PHP でも使用できるようになります。
<constant> タグの内部に、 その定数についての説明のテキストを含めることができます。 このテキストは、DocBook XML ドキュメントのソースで利用されます。
Example 22. PHP の定数
...
<constants>
<constant name="SAMPLE_INT" type="int" value="42">
integer の定数の例
</constant>
<constant name="SAMPLE_FLOAT" type="float" value="3.14">
float の定数の例
</constant>
<constant name="SAMPLE_FLOAT" type="float" value="M_PI">
float の定数で、#define 済みの定数の値を使用する例
</constant>
<constant name="SAMPLE_STRING" type="string" value="Hello World!">
文字列定数の例
</constant>
<constant name="SAMPLE_INT2" type="int" value="23">
これは、生成されるヘッダファイルに
<literal>#define SAMPLE_INT2 23</literal>
という定義を追加します
</constant>
<constant name="MY_CONST">
すでに #define されている整数の定数は、このように使用できます
</constant>
</constants>
...
|
エクステンション内で変数を定義し、 そのエクステンション全体もしくは特定のリクエスト内のグローバル変数として使用することができます。 エクステンション全体で使用する真のグローバル変数については、 特別な登録作業は不要です。グローバルの <code> タグの中で C のコードとして定義するだけで使用できます。
ひとつのリクエスト内でだけグローバルとなるモジュールグローバル変数については、 スレッドセーフであることと リクエストの初期化に初期化されることを確実にしなければなりません。 php.ini ディレクティブの値もモジュールグローバルとして格納されますが、 これについてはさらに追加の定義が必要です。
すべてのグローバル定義は <globals> 環境の中に書く必要があります。 モジュールグローバルについては、 <global> タグで定義します。 php.ini ディレクティブの定義には <phpini> タグを使用します。
<global> 定義には name=... 属性と type=... 属性が必須です。それぞれ、C 言語で使用できる形式の名前と型を設定します。 C の型としてどんなものが設定できるかは、 どんなヘッダファイルをインクルードしているかによって異なります。 pecl-gen が XML ファイルをパースする時点では どんな型が使用できるのかはわからないので、 型のチェックについては「妥当な名前かどうか」しか調べません。 C の基本型やインクルードしたファイルで定義されている型以外の型を指定すると、 生成されたコードをあとでコンパイルするときにエラーが発生します。
初期値を指定するには value=... 属性を使用します。 この機能は、単純な数値の場合にのみ使用するようにしましょう。 複雑な値の場合は、エクステンションの RINIT() 関数内で初期化することをお勧めします。
php.ini ディレクティブは、 <globals> 環境の中で <phpini> を用いて定義します。 php.ini ディレクティブを定義する際には、 その名前と型そしてデフォルト値を name=...、type=... および value=... で指定しなければなりません。
ディレクティブの名前は、C 言語で使用できる形式の名前となります。 実際のディレクティブ名は、エクステンション名の後にドット '.' をひとつつけ、 その後にここで指定した名前を続けたものとなります。 型として指定できるのは bool か int、float、string のいずれかです。
ディレクティブのデフォルト値は文字列としてエンジンに渡されます。 そのため、C の定数やプリプロセッサマクロをここで使用することはできません。 デフォルト値を表す文字列は、そのディレクティブ用に登録された OnUpdate ハンドラでパースされます。 エクステンションのコード生成やコンパイルの際には 値のチェックは行われません。値のチェックを行うのは 登録された OnUpdate ハンドラで、 このハンドラはリクエストの初期化時に実行されます。 OnUpdate ハンドラは、デフォルトで内部関数の OnUpdatetype ハンドラをコールします。別のハンドラを使用する場合は onupdate=... 属性で指定します。
ディレクティブの値は、好きなときに変更できます。これを制限するには access=... 属性を設定します。 指定できる値は次のとおりです。
php.ini あるいはウェブサーバの設定でのみ変更可能
ローカルの .htaccess ファイルで変更可能
PHP のコード内で変更可能
いつでも誰でも変更可能
<phpini> タグの内部に記述した内容は、 そのディレクティブのドキュメントを生成する際に利用されます。 <global> 定義の内部にもテキストを記述することができますが、 これは内部のドキュメントにしか利用されません。 (現時点では) DocBook XML には書き出されません。
Example 23. グローバル変数や ini エントリの定義
...
<globals>
<global name="sample_int" type="int" value="42" />
<global name="sample_float" type="float" value="3.14" />
<global name="SAMPLE_STRING" type="char *" />
<phpini name="my_int" type="int" value="42" onupdate="OnUpdateLong" access="all">
"sample.my_int" ディレクティブについての説明
</phpini>
</globals>
...
|
モジュールグローバルや ini パラメータへのアクセスには、 スレッドセーフな EXTNAME_G() マクロ (EXTNAME は、作成するエクステンションの名前を大文字にしたものに読み替えてください) を使用します。
PHP のリソース型は、 <resources> 環境の中で定義します。 個々の <resource> について、 name=... 属性と payload=... 属性を指定しなければなりません。 name は C 言語の名前として妥当な形式、 payload も C 言語の型として妥当な型でなければなりません。 payload の型のチェックは、あくまでも正しい形式かどうかしか調べません。 その型の定義が実際にヘッダファイルに含まれているかどうかなんて、 エクステンションのコードを生成する時点では知りようがありませんから。
実際のリソースデータ構造体には、 payload 型へのポインタが含まれます。 実際の payload のメモリ確保や開放を PHP に任せたい場合は、 alloc=... 属性を "yes" と設定します。 ライブラリの関数や自分自身で行う場合は、 alloc=... を "no" と設定します (デフォルトは no です)。
リソースが破棄されるのは、 そのリソースを参照する最後の変数が削除されたときか、 リクエストが終了するときです。 もしリソースの中身について何らかの後始末が必要な場合は、 適切な C 言語のコードを <destruct> タグで記述する必要があります。 ここで書くコードでは、リソースの中身を表す resource ポインタ変数を使用することができます。
リソースの中身の確保を PHP に任せている (alloc="yes") 場合は、自分で後始末のコードを書く必要はありません。 また、メモリの開放以外に特に追加の作業は不要です。
Example 25. リソース
...
<resources>
<resource name="sample_resource" payload="float" alloc="yes">
<description>
単純な浮動小数点数値リソース
</description>
<!-- no <destruct> needed due to the alloc attribute -->
</resource>
<resource name="sample_struct" payload="struct foobar" alloc="no">
<description>
foobar リソース。実際の管理は外部の foobar ライブラリが行う
</description>
<destruct>
foobar_release(resource);
</destruct>
</resource>
</resources>
...
|
リソースのインスタンスの作成は、 <resource> では行いません。 これは、PHP のパブリック関数が行う作業です。
Example 26. リソースの作成
<function name="foo_open">
<proto>resource foo foo_open(string path)</proto>
<code>
return_res = foo_open(path);
if (!return_res) RETURN_FALSE;
</code>
</function>
|
リソースの開放には FREE_RESOURCE() マクロを使用します。リソースを開放する際には、 リソースのデストラクタ関数が自動的にコールされます。
![]() | オブジェクト指向の API は、PHP 4 と PHP 5 で異なります。 CodeGen_PECL がサポートするのは、PHP 5 の API だけです。 |
クラスの宣言には class コンテナを使用します。
各クラスには、name=...
属性を用いて一意なクラス名を指定する必要があります。
extends=... 属性を使用すると、 別の内部クラスから継承させることもできます。 継承元のクラスは、必ずしも同じパッケージで定義されている必要はありません。 したがって、指定した名前のクラスが存在するかどうかのチェックは行いません。 存在しないクラスを継承しようとしたときのエラーは、 PHP の実行時にしか捕捉できません。
Example 29. 単純な継承
<class name="parent">
<function name="foo">
<proto>int foo()</proto>
<code>RETURN_LONG(42);</code>
</function>
<function name="bar">
<proto>float bar()</proto>
<code>RETURN_DOUBLE(3.14);</code>
</function>
</class>
<!-- 子クラスで foo() をオーバーライドします -->
<class name="child" extends="parent">
<function name="foo">
<proto>int foo()</proto>
<code>RETURN_LONG(23);</code>
</function>
</class>
|
オプションの属性 abstract=... や final=... を使用すると、abstract クラス (派生クラスは作成できるが、インスタンスは作成できないクラス) や final クラス (これ以上派生させることができないクラス) を作成することができます。
クラスのメソッド (メンバ関数) の定義は、通常の関数と同様に
function タグで行います。
グローバル関数の場合の function
と同じです。
access=... 属性は、そのメソッドのアクセス制限を定義します。 使用できる値は public か protected、private のいずれかです (しかし、private なネイティブメソッドを作っても何の意味もありません)。
abstract 属性と final 属性を使用すると、そのメソッドを abstract あるいは final として宣言することができます。 これは、PHP の同名のキーワードと同じ意味となります。
procedural=... 属性を指定すると、 そのメソッドに対応する手続き型の関数を登録します。 この関数は、クラスのインスタンスを最初の引数として受け取るものです。
$object->method(...parameters...) |
function($object, ...parameters...) |
この機能が便利なのは、これまではリソース型を使用していたようなエクステンションを オブジェクト指向で実装しなおす場合です。例としては、 ext/mysql の後継となる ext/mysqli などがあります。
単に procedural='yes' を使用した場合 関数名は classname_methodname となります。その他の促成の値も関数名として使用します。
クラスのプロパティを定義するには property
タグを使用します。プロパティの名前は name=...
で指定します。これは必須となります。
プロパティへのアクセス制限は、デフォルトでは public となります。 オプションの access=... 属性で、 public、protected あるいは private のいずれかを設定することができます。
プロパティのデフォルトの型や値を指定するには、オプションの属性 type=... や value=... を使用します。 型として指定できるのは long、double、 string そして NULL です。 これらの属性を指定しなかった場合、プロパティの型と値のデフォルトは NULL となります。
static プロパティを宣言するには static=... 属性を使用します。static なプロパティは、 そのクラスのすべてのインスタンスで共有されます。
インターフェイスを宣言するには interface
タグを使用します。このタグには 2 つの属性があります。
name=... 属性は必須で、
インターフェイスの名前を指定します。もうひとつの属性
extends=... はオプションで、
このインターフェイスが他の既存のインターフェイスを継承している場合に指定します。
継承元のインターフェイスが別のエクステンションで定義されている場合は、
エクステンションの依存性も宣言する必要があります。
インターフェイスのメソッドを宣言するには function
タグを使用します。このコンテキストでサポートする属性は
name=... だけです。
というのも、インターフェイスのメソッドは常に public かつ abstract
であり、決して final となることはないからです。
function の内部で
proto を定義する必要がありますが、
インターフェイスのメソッド宣言の中では
code や test
といったタグは使用できません。
クラスを作成する際にはひとつあるいは複数のインターフェイスを実装することができます。
あるクラスが実装するインターフェイスを宣言するには、
implements タグとその属性
interface=... を使用します。
実装するインターフェイスは、そのエクステンションの定義ファイルで定義されている
(このセクションの "インターフェイス" を参照ください) か、
エクステンションの依存性で定義した他のエクステンションで定義されている必要があります。
Unix/Cygwin でのビルド用に生成する config.m4 ファイルにチェック項目を追加したい場合は、<configm4> タグを使用します。'position' 属性を使用すると、 そのコードを config.m4 の先頭付近に追加するか末尾付近に追加するかを指定できます。
Unix/Cygwin でのビルド用の Makefile にルールを追加するには <makefile> を使用します。 これにより、デフォルトの自動生成されるルール以外の依存性やビルドルールを追加できるようになります。
グローバルなテストケースを作成するには <test> タグを使用します。各関数のテストケースは自動的に作成されます。
現状では、作成したエクステンションをまず php.ini でロードさせるようにする必要があります。 また、テストスイートを実行するためには次の手順に従わなければなりません (お使いのシステムにあわせて、パスの部分は適宜変更してください)。
TEST_PHP_EXECUTABLE="/usr/local/bin/php" php path/to/run-tests.php tests |
PHP 5.1 以降では、PECL エクステンションのテストはソースディレクトリで単に make test とするだけで実行できるようになります。 現在この機能のための変更点をレビュー中で、 うまくいけば PHP 5.1.0 のリリースに含まれる予定です (訳注: PHP 5.1.0 が出る前に書いた文章?)。
グローバルなテストケースのスクリプトを作成するには <test> タグを使用します。 <test> には name 属性が存在します。テストの名前を基にしたファイル名でファイルを作成するので、 name は一意なものである必要があります。また、 英数字と '-'、'_' しか使用することはできません。よりわかりやすい形式のテスト名を指定するには、 <test> の内部で <title> タグを使用します。
実際に実行させる PHP コードは、<code> セクションで記述します。テスト結果として期待する出力の内容を <result> タグで指定します。デフォルトは OK です。PHP のテストスイートは、 テスト結果の出力と期待値との比較を三通りの方法で行います。 それぞれ、通常の文字列としての比較、printf 形式のプレースホルダ (%d は数値など) を使用した比較、そして正規表現を用いた比較です (詳細は、PHP ソースの README.TESTING* を参照ください)。デフォルトでは plain モードを使用します。 それ以外を使用するには、<result> の mode 属性を format あるいは regex に設定します。
生成されたテスト内の --SKIPIF-- セクションは、その拡張モジュールがロードされているかどうかを調べます。 もしロードされていない場合はそのセクションのテストを自動的にスキップします。 それ以外にスキップ条件を追加するには <skipif> タグを使用します。 このタグの中身は、PHP の式あるいはコード片となります。 式の場合は、評価結果が true の場合にテストをスキップします。コード片の場合は、 その出力が skip となる場合にテストをスキップします。この場合、skip に続けてテストをスキップした理由を説明することもできます。
テスト時に使用する追加の php.ini 設定については <ini> セクションで指定します。
各関数について、デフォルトのテストケースが作成されます。 このテストの名前とタイトルは、関数名をもとに自動的に決まります。
テストコードと期待値は、<function> の中の <test> セクションで指定します。 <code> や <result>、 <skipif>、そして <ini> についても、 グローバルセクションと同じように使用できます。 関数のテストでは、 <test> タグあるいは <title> タグの name 属性はサポートしていません。
CodeGen_PECL が使用している XML パーサは、 三通りの方法で外部ファイルのインクルードをサポートしています。
外部エンティティ
XInclude のサブセット
<code> タグの source 属性
SGML や初期の XML では、 システムエンティティによるインクルードが唯一の仕組みでした。 システムエンティティは、ドキュメントの DOCTYPE ヘッダで定義する必要があります。 そして、後でドキュメントの中でそのエンティティを用いると 指定したファイルをインクルードすることができます。
CodeGen の XML パーサは、 XInclude の単純なサブセットをサポートしています。 <include> タグの href=... 属性を使用して、 追加の設定ファイルをインクルードできるようになります。
Example 2. XInclude
<extension name="foobar" xmlns:xi="http://www.w3.org/2001/XInclude">
...
<xi:include href="foobar_2.xml"/>
...
</extension>
|
parse=... 属性もサポートしています。 <include parse='text' href='...'/> とすると、任意のデータを (XML として解釈せずにそのまま) インクルードできるようになります。
Example 3. そのままの状態での XInclude
<extension name="foobar" xmlns:xi="http://www.w3.org/2001/XInclude">
...
<description><xi:include href="README" parse="text"/></description>
...
</extension>
|
<include> のそれ以外の機能や <fallback> タグについてはまだサポートしていません。 それらの大半については、この場面で使っても無意味なものです。
C のコードには、通常は > や <、そして & が含まれています。これらの文字は XML 内ではエスケープしなければなりません。 対策としては、それらの文字をすべてエンティティに変換するか、 あるいはコードを CDATA セクション内に記述します。
何度も何度も <![CDATA[ とタイプするのは大変です (特にドイツ語のキーボードだと面倒です)。そこで、 CodeGen の XML パーサには処理命令 <?data を追加することにしました。 これは CDATA と同じように使えるものです。
XMP スペックファイルをエクステンションのディレクトリに変換するには、 XML ファイル名を引数として pecl-gen コマンドを実行します。
pecl-gen my_extension.xml |
-f オプションあるいは --force オプションを指定しない限り、 pecl-gen は既存のファイル群を上書きしません。
pecl-gen -f my_extension.xml |
コンパイルする前に、実際にビルドするシステム上で configure をしなければなりません。PECL エクステンションの configure は、次の二段階で行います。
まず、PHP のインストール先からエクステンションのディレクトリにいくつかのファイルをコピーし、 autotool を実行して configure ファイルを作成します。 これは、PHP に含まれる phpize コマンドを実行するだけで大丈夫です。
cd my_extension phpize |
次に configure を実行し、システムの状態に合わせて拡張モジュールを設定します。 たいていの場合は単に configure を実行するだけで十分で、そうすれば適切なデフォルトが自動的に選択されます。 そのエクステンションで使用する外部ライブラリが非標準の場所にインストールされている場合は、 configure で適切な --with-... オプションを指定します。
configure |
PHP 5.1 以降では、単に make test とするだけで自動生成されたテストケースが実行できるようになりました。 それより前のバージョンの PHP では、いくつかの準備が必要です。
テストに使用する PHP バイナリを指定する
エクステンションのテストに使用する php.ini を設定する
PHP のソースに同梱されている run-tests.php を手動で実行する
TEST_PHP_EXECUTABLE="/usr/local/bin/php" php ../php-src/run-tests.php tests |
(php バイナリや run-tests.php スクリプトの場所は、場合によって当然異なることでしょう)
作成したエクステンションをデフォルトのディレクトリにコピーするには、 make install を実行します。php.ini でデフォルトとは異なる extension_dir を指定している場合は、そのディレクトリにエクステンションの .so ファイルを手動でコピーしなければなりません。
どちらにしても、一般ユーザの権限ではおそらくエクステンションのインストールはできないでしょう。 sudo を使用するなどして 適切なユーザでコマンドを実行する必要があります。
sudo make install |
pecl-gen の生成するファイルの中には、 PEAR パッケージ用の定義ファイルも含まれています。 古い形式の package.xml、 新しい形式の package2.xml の両方に対応しています。
エクステンションの configure、コンパイルそしてインストールを一括で行うには PEAR インストーラを使用します。
上のようにすると、configure からコンパイル、そしてインストールまでのすべての処理を行います。 また、 PECL リポジトリやその他の PECL 互換チャネルでの配布用のパッケージを作成することもできます。
pecl.php.net 以外のチャネルでパッケージを公開したい場合は、 定義ファイルの中に <channel> タグを追加してチャネル名を指定する必要があります。