5.PHP 内部の基礎知識¶
5.1.PHP のソースツリー¶
PHP Extension の開発するにあたっては、残念ながらまとまったドキュメントがありません。このため、本格的に開発を始めると、結局は既存の PHP ソースを grep しまくって似たような処理がないかを探し、それを真似るということを繰り返すことになります。そのためには、どこを重点的に探せばいいかを知っておく必要があります。grep 対象として重要なのは、以下のディレクトリです。
No. | ディレクトリ名 | 内容 |
---|---|---|
1 | Zend | Zend Engine。API はここに定義があるので、一番お世話になるところです。 |
2 | ext | Extension。既存の拡張モジュールのソースです。
この中から似たような処理を見つけることになります。
|
3 | main | PHP コア。内部の共通関数等があり、ext 配下の
Extension から参照されています。
|
5.2.疑似グローバル変数¶
Zend Engine への入口となる Zend API は、巨大なマクロのかたまりです。今後 ext 配下にある既存のソースを参照しつつ、テンプレートの php/ext/my_ext.c に手を入れていきます。既存のソースを見ていると、その中にはいかにもグルーバル変数のような顔をしているパラメーター変数があったりします。事前に知っておくとソースが追いやすくなるので、最初にご紹介しておきます。
5.2.1.モジュール名¶
今回のケースでは、my_ext.c の中で以下のように各マクロへの引数として指定されている my_ext
です。
PHP_MINIT_FUNCTION(my_ext)
PHP_MSHUTDOWN_FUNCTION(my_ext)
PHP_MINFO_FUNCTION(my_ext)
PHP_MINIT_FUNCTION マクロを例に取り、引数の扱いを調べてみます。
~/php$ grep -r PHP_MINIT_FUNCTION . | grep define
./main/php.h:#define PHP_MINIT_FUNCTION ZEND_MODULE_STARTUP_D
~/php$ grep -r 'define ZEND_MODULE_STARTUP_D' .
./Zend/zend_API.h:#define ZEND_MODULE_STARTUP_D(module) int ZEND_MODULE_STARTUP_N(module)(INIT_FUNC_ARGS)
~/php$ grep -r 'define ZEND_MODULE_STARTUP_N' .
./Zend/zend_API.h:#define ZEND_MODULE_STARTUP_N(module) zm_startup_##module
~/php$ grep -r 'define INIT_FUNC_ARGS' .
./Zend/zend_modules.h:#define INIT_FUNC_ARGS int type, int module_number
./Zend/zend_modules.h:#define INIT_FUNC_ARGS_PASSTHRU type, module_number
つまり PHP_MINIT_FUNCTION(my_ext)
は、C のプリプロセッサにより zm_startup_my_ext(int type, int module_number)
という関数定義に展開されるということがわかります。 ##
は文字列の連結を指示するためのプリプロセッサへの命令です。zm_startup_my_ext() という関数名は間接的に生成されたものなので、これをソースツリー内で grep しても出て来ませんが、gdb でデバッグする際にはこれらのシンボルが見えるようになります。
5.2.2.type/module_number¶
PHP_MINIT_FUNCTION() マクロは my_ext
Extension モジュールをロードした後、最初に呼び出される処理です。上記の grep の結果より、これに対して type と module_number を渡していることがわかります。他にも同様のマクロがあるかもしれません。これらのマクロブロックの中では、 type
と module_number
はあたかもグローバル変数のように見えます。
5.2.3.return_value¶
PHP から認識できる PHP 関数のエントリは、内部的には Zend/zend_API.h で以下のように定義されています。
typedef struct _zend_function_entry {
const char *fname;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
const struct _zend_internal_arg_info *arg_info;
uint32_t num_args;
uint32_t flags;
} zend_function_entry;
fname が(PHPから見える)関数名、handler が実際の飛び先です。handler に引数として与えられている INTERNAL_FUNCTION_PARAMETERS の定義は以下の通りです。
~/php$ grep -r 'define INTERNAL_FUNCTION_PARAMETERS' .
./Zend/zend.h:#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value
return_value はユーザーランド(PHP スクリプト)に返される値です。たとえば PHP の printf() 関数は、公式ドキュメントによると以下のように定義されています。
-
int printf ( string $format [, mixed $args [, mixed $... ]] )
この定義に従って printf() を実装する C の内部関数は、return_value に対して PHP の integer 型に相当する値をセットして返す必要があります。
5.3.Extension ソースの構造¶
5.3.1.zend_module_entry¶
my_ext/my_ext.c の最後の方に、この拡張モジュール全体の構造を示すモジュールエントリの構造体があります。
zend_module_entry my_ext_module_entry = {
STANDARD_MODULE_HEADER,
"my_ext",
my_ext_functions,
PHP_MINIT(my_ext),
PHP_MSHUTDOWN(my_ext),
PHP_RINIT(my_ext), /* Replace with NULL if there’s nothing to do at request start */
PHP_RSHUTDOWN(my_ext), /* Replace with NULL if there’s nothing to do at request end */
PHP_MINFO(my_ext),
PHP_MY_EXT_VERSION,
STANDARD_MODULE_PROPERTIES
};
zend_module_entry の構造体定義は Zend/zend_modules.h にあります。この構造体の中身は、ほぼ変更する必要はありません。開発にあたっては、必要に応じてこれらのマクロの中身を埋めていきます。
PHP_
で始まるマクロは main/php.h で定義されています。これらの役目は以下の通りです。[1]
No. | マクロ名 | 説明 |
---|---|---|
1 | PHP_MINIT | このモジュールが最初にロードされた際に呼ばれるコールバック関数。 |
2 | PHP_MSHUTDOWN | このモジュールがアンロードされる時(通常はシャットダウン時)
に呼ばれるコールバック関数。
|
3 | PHP_RINIT | 各リクエストの開始時に呼ばれるコールバック関数。 |
4 | PHP_RSHUTDOWN | 各リクエストの終了時に呼ばれるコールバック関数。 |
5 | PHP_MINFO | phpinfo() 関数(php -m)が呼び出された際に呼ばれるコールバック関数。 |
6 | PHP_(モジュール名)
_VERSION
|
そのモジュールのバージョン情報。
ext_skel がphp_my_ext.h の中にデフォルトの定義を作成します。必要に応じて Extension の作者が上書きして指定します。
|
初期化処理や終了処理が複数あるのは、ライフサイクルの違いから来るものです。PHP のライフサイクルは SAPI 毎に異なります。以下に CLI のケースと Apache Prefork MPM のケースを示します。
RINIT / RSHUTDOWN はリクエストのたびに呼ばれるため、これらの処理を極力軽くすることが全体のパフォーマンス向上につながります。これらの処理が不要な場合は、 zend_module_entry のメンバーの値として RINIT / RSHUTDOWN の代わりに NULL を指定します。
5.3.2.zend_function_entry¶
前述の zend_module_entry の3番目のプロパティとして、 my_ext_functions
があります。その実体は、 zend_module_entry の直前に定義があります。
const zend_function_entry my_ext_functions[] = {
PHP_FE(confirm_my_ext_compiled, NULL) /* For testing, remove later. */
PHP_FE_END /* Must be the last line in my_ext_functions[] */
};
この構造体は、この拡張モジュールでユーザーランドに対して PHP 関数として公開する、関数エントリの一覧です。デフォルトでは、 ext_skel
が1つだけ confirm_my_ext_compiled
関数を定義しています。この構造体に必要な分だけ関数エントリを追加していきます。ちゃんと動くようになってきたら、 confirm_my_ext_compiled
のエントリとその関数定義の実体は削除して構いません。エントリの末尾は PHP_FE_END で閉じます。
PHP_FE や PHP_FE_END マクロは main/php.h で定義されています。といっても、 PHP_*
マクロの実際の定義は Zend/zend_API.h にあるエントリの別名であることが多いです。
~/php$ grep -rw PHP_FE . | grep -w define
./main/php.h:#define PHP_FE ZEND_FE
~/php$ grep -rw '#define ZEND_FE'
Zend/zend_API.h:#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
~/php$ grep -rw '#define ZEND_FENTRY'
Zend/zend_API.h:#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags },
~/php$ grep -rw '#define ZEND_FN'
Zend/zend_API.h:#define ZEND_FN(name) zif_##name
'#'
ディレクティブは実引数を文字列化します("
で囲む)。最終的に PHP_FE(confirm_my_ext_compiled, NULL)
は { "confirm_my_ext_compiled", zif_confirm_my_ext_compiled, ... }
のように展開されるため、confirm_my_ext_compiled
というシンボルは zif_confirm_my_ext_compiled
という内部関数名に変換されることになります。
zend_function_entry と同様に、クラスを定義する zend_class_entry などもありますが、スケルトンとしては生成されないようです。これらの定義は Zend/zend_types.h にあります。
[1] | PHP 公式マニュアルにも zend_module 構造体 というページに詳細な説明があるのですが、残念ながら PHP 5.2 の時点までで更新が止まっています。 |
[2] | (1, 2) 出典 Extending and Embedding PHP |