さあ、今やあなたは安全なビルド環境を手にいれ、 モジュールを PHP に組み込めるようになりました。 そろそろ全体像について話を始める時期でしょう。
すべての PHP モジュールは、共通の構造に従っています。
ヘッダファイルのインクルード (必要なすべてのマクロ、API、define などをインクルードするため)
エクスポートする関数の C での宣言 (Zend 関数ブロックの宣言のために必要)
Zend 関数ブロックの宣言
Zend モジュールブロックの宣言
get_module() の実装
エクスポートするすべての関数の実装
モジュールに組み込まなければならない唯一のヘッダファイルは php.h で、これは PHP ディレクトリにあります。 このファイルは、新しいモジュールを使用できるようにビルドするための すべてのマクロ定義、API 定義を含みます。
豆知識: モジュール固有の定義を含むヘッダファイルを、 別に分けて作成しておくとよいでしょう。 エクスポートされるすべての関数の定義をこのヘッダファイルに含め、 そしてこのファイルで php.h をインクルードします。 ext_skel を使用して雛形を作成したのなら、 すでにこの形式のヘッダファイルが出来上がっているはずです。
エクスポートする (つまり PHP の新しいネイティブ関数として使用できるようにする) 関数を宣言するためには、Zend が提供するマクロを使用します。 宣言は、このように行います。
ZEND_FUNCTION ( my_function ); |
ZEND_FUNCTION は、Zend の内部 API を満たす新しい C 関数を宣言します。内部 API を満たすとは、 その関数の型が void であり、パラメータとして INTERNAL_FUNCTION_PARAMETERS (別のマクロ) を受け取るということです。さらに、関数名の先頭に zif を付加します。これらの定義を満たす宣言の例は、次のようになります。
void zif_my_function ( INTERNAL_FUNCTION_PARAMETERS ); |
void zif_my_function( int ht , zval * return_value , zval * this_ptr , int return_value_used , zend_executor_globals * executor_globals ); |
インタプリタおよびエグゼキュータのコアは PHP 本体のパッケージとは分離されているので、 マクロや関数群を定義するもうひとつの API が作られました。これが Zend API です。今のところ Zend API は、 かつて PHP が担当していた機能のうちのごくわずかしか処理できません。 PHP の関数の多くは Zend のマクロに書き下ろされており、 その内部で Zend API をコールしています。 おすすめのやりかたは、まずできる限り Zend API を使用することです。 というのは古い API は互換性のためだけに残されているものだからです。 例えば、zval 型と pval 型はまったく同じものです。 zval は Zend での定義で、pval は PHP での定義です (実際のところ、現在 pval は zval のエイリアスとなっています)。 INTERNAL_FUNCTION_PARAMETERS は Zend のマクロなので、 上の宣言には zval が用いられています。 コードを書く際には、新しい Zend API のために常に zval を使うようにしましょう。
この宣言のパラメータリストは非常に重要です。これらは常に頭に入れておくようにしましょう。 (表46-1 を参照ください)。
表 46-1. PHP からコールされた際に関数に渡される Zend のパラメータ
パラメータ | 説明 |
ht | Zend の関数に渡す引数の数。これを直接操作してはいけません。値を取得する際には ZEND_NUM_ARGS() を使用してください。 |
return_value | この変数は、あなたの関数から PHP に返す値を渡すために使用します。 この変数にアクセスするには、定義済みマクロを使用するのがベストです。 マクロについては後で説明します。 |
this_ptr | この変数を使用すると、関数がオブジェクト内で使用されている場合に そのオブジェクトにアクセスすることができます。このポインタを取得するには 関数 getThis() を使用します。 |
return_value_used | このフラグは、この関数の返す値が実際に呼び出し元のスクリプトで使われるのかどうかを指定します。 0 は、返り値が使われないことを表します。 1 は、呼び出し元が返り値を期待していることを表します。 このフラグの値により、関数が正しく使用されているかどうかを確認します。また、 値を返す処理に負荷がかかる場合などに、このフラグによって速度の最適化を行います (array.c で、このような使用法を用いています)。 |
executor_globals | この変数は、Zend エンジンのグローバル設定へのポインタです。 これが便利なのは、例えば新しい変数を作成するときです (詳細はあとで説明します)。 エグゼキュータのグローバル設定は、マクロ TSRMLS_FETCH() を使うことによってもあなたの関数で使用できます。 |
エクスポートする関数の宣言が完了しました。こんどはさらにそれを Zend に登録しなければなりません。関数のリストを登録するには、 zend_function_entry の配列を使用します。 この配列には、外部に公開するすべての関数について PHP で使用する場合の名前と C ソース内で定義されている名前が含まれています。 内部的には、zend_function_entry は 例46-4 のように定義されています。
zend_function_entry firstmod_functions[] = { ZEND_FE(first_module, NULL) {NULL, NULL, NULL} }; |
注意: 最後を表す印として、定義済みマクロを使用することは できません。そうすると、 "NULL" という名前の関数を探しにいってしまいます!
マクロ ZEND_FE ('Zend Function Entry' を省略したものです) は、構造体のエントリを単純に zend_function_entry に展開します。 これらのマクロは特別な命名規約を持っていることに注意しましょう。 あなたが作成する C 関数の名前には、前に zif_ をつけることになります。つまり、ZEND_FE(first_module) は zif_first_module() という名前の C 関数を参照するということです。このマクロと手書きのエントリを混ぜて使用する場合には (お勧めしません) これを頭に入れておきましょう。
豆知識: zif_*() という名前の関数でコンパイルエラーが出た場合は、 ZEND_FE で定義した関数がかかわっています。
表46-2 が、 関数の定義に使用できるすべてのマクロの一覧です。
表 46-2. 関数定義用のマクロ
マクロ名 | 説明 |
ZEND_FE(name, arg_types) | zend_function_entry に name という名前の関数エントリを定義します。対応する C の関数が必要です。 arg_types は NULL に設定しなければなりません。 この関数は、自動的に C の関数名を生成します。その名前は PHP の関数名の先頭に zif_ をつけたものになります。 例えば ZEND_FE("first_module", NULL) とすると PHP の関数 first_module() を登録したことになり、 それを C の関数 zif_first_module() と関連付けます。 ZEND_FUNCTION と組み合わせて使用します。 |
ZEND_NAMED_FE(php_name, name, arg_types) | php_name という名前で PHP の関するを定義し、 それを対応する C の関数 name に関連付けます。 arg_types は NULL に設定しなければなりません。ZEND_FE のように自動的に名前を決められたくない場合にこの関数を使用します。 ZEND_NAMED_FUNCTION と組み合わせて使用します。 |
ZEND_FALIAS(name, alias, arg_types) | alias という名前で、 name のエイリアスを定義します。 arg_types は NULL に設定しなければなりません。対応する C の関数は不要です。 その代わりに、エイリアスの対象を参照します。 |
PHP_FE(name, arg_types) | 古い PHP の API で、ZEND_FE と同じものです。 |
PHP_NAMED_FE(runtime_name, name, arg_types) | 古い PHP の API で、ZEND_NAMED_FE と同じものです。 |
注意: ZEND_FE と PHP_FUNCTION を組み合わせて使用したり、あるいは PHP_FE と ZEND_FUNCTION を組み合わせて使用したりすることはできません。しかし、 各関数について ZEND_FE と ZEND_FUNCTION、あるいは PHP_FE と PHP_FUNCTION という組み合わせが守られているのなら、 それらを混用することは可能です。 しかし、混用することは 推奨しません。 ZEND_* マクロだけを使用するようにしましょう。
このブロックは構造体 zend_module_entry に保存され、モジュールの内容について Zend に示すために必要な すべての情報が含まれます。このモジュールの内部定義は 例46-5 で確認できます。
今回の例では、この構造体を次のように実装します。
zend_module_entry firstmod_module_entry = { STANDARD_MODULE_HEADER, "First Module", firstmod_functions, NULL, NULL, NULL, NULL, NULL, NO_VERSION_YET, STANDARD_MODULE_PROPERTIES, }; |
参照用として、スタートアップ関数およびシャットダウン関数に関するマクロを 表46-3 にまとめておきます。 これらは今回の例では使用しませんが、後で説明します。 スタートアップ関数やシャットダウン関数を宣言する際にはこれらのマクロを使用すべきです。 というのもこれらの関数には特別なパラメータ (INIT_FUNC_ARGS および SHUTDOWN_FUNC_ARGS) を渡さなければならず、 定義済みマクロを使用することでこれらが自動的に関数宣言に組み込まれるからです。 仮にこれらの関数を (マクロを使用せずに) 手動で宣言したとしましょう。 もし PHP の開発者が何らかの事情で引数を変更したとすると、 それに追従するためにあなたは自分のモジュールのソースを変更しなければならなくなります。
表 46-3. スタートアップ関数、シャットダウン関数を宣言するためのマクロ
マクロ | 説明 |
ZEND_MINIT(module) | モジュールの開始時の関数を宣言します。 関数名は zend_minit_<module> (例えば zend_minit_first_module) のようになります。 ZEND_MINIT_FUNCTION と組み合わせて使用します。 |
ZEND_MSHUTDOWN(module) | モジュールのシャットダウン時の関数を宣言します。 関数名は zend_mshutdown_<module> (例えば zend_mshutdown_first_module) のようになります。 ZEND_MSHUTDOWN_FUNCTION と組み合わせて使用します。 |
ZEND_RINIT(module) | リクエストの開始時の関数を宣言します。 関数名は zend_rinit_<module> (例えば zend_rinit_first_module) のようになります。 ZEND_RINIT_FUNCTION と組み合わせて使用します。 |
ZEND_RSHUTDOWN(module) | リクエストのシャットダウン時の関数を宣言します。 関数名は zend_rshutdown_<module> (例えば zend_rshutdown_first_module) のようになります。 ZEND_RSHUTDOWN_FUNCTION と組み合わせて使用します。 |
ZEND_MINFO(module) | モジュール情報を出力する関数を宣言します。 phpinfo() がコールされた際に使用されます。 関数名は zend_info_<module> (例えば zend_info_first_module) のようになります。 ZEND_MINFO_FUNCTION と組み合わせて使用します。 |
これは特別な関数で、すべての動的読み込みモジュールで使用されます。 これを作成するため、まずは ZEND_GET_MODULE を見てみましょう。
#if COMPILE_DL_FIRSTMOD ZEND_GET_MODULE(firstmod) #endif |
関数の実装が、条件付きコンパイル文で囲まれています。なぜかというと、 get_module() 関数が必要となるのは あなたのモジュールが動的モジュールとしてビルドされる場合だけだからです。 コンパイラのコマンドで COMPILE_DL_FIRSTMOD を定義することにより (動的モジュールとしてビルドするための コンパイル手順については上記を参照ください)、 動的モジュールとしてビルドするのか組み込みモジュールとしてビルドするのかを 指示することができます。組み込みモジュールを作成する場合は、 get_module() の実装は単純に取り除かれます。
get_module() は、 Zend がモジュールを読み込む際にコールされます。 例えば、スクリプト内で dl() がコールされた場合などです。この関数の目的は、モジュール情報ブロックを Zend に返し、モジュールの内容をエンジンに教えることです。
動的モジュールで get_module() 関数を実装しなかった場合、 Zend がそのモジュールにアクセスしようとした際にエラーとなります。
あとは、エクスポートする関数を実装すれば終わりです。 first_module では、例としてこのような関数を使用します。
ZEND_FUNCTION(first_module) { long parameter; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", ¶meter) == FAILURE) { return; } RETURN_LONG(parameter); } |
関数宣言の後には、引数のチェックおよびその内容の取得、 引数の変換、そして返り値の作成などのコードが続きます (これらの詳細は後述します)。
基本的に、これですべてです。PHP モジュールを構成するのに、 これ以上のものは必要ありません。組み込みのモジュールについても その構造は動的モジュールと同じです。 ここまでに説明した内容を身に着けておけば、今後 PHP モジュールのソースファイルを読んでいく際につまづくこともなくなるでしょう。
さあ、これ以降の節で PHP の内部構造について学び、 強力な拡張モジュールを作っていきましょう。