ソースコードについての議論

さあ、今やあなたは安全なビルド環境を手にいれ、 モジュールを PHP に組み込めるようになりました。 そろそろ全体像について話を始める時期でしょう。

モジュールの構造

すべての PHP モジュールは、共通の構造に従っています。

ヘッダファイルのインクルード

モジュールに組み込まなければならない唯一のヘッダファイルは 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 );
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 での定義です (実際のところ、現在 pvalzval のエイリアスとなっています)。 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 に登録しなければなりません。関数のリストを登録するには、 zend_function_entry の配列を使用します。 この配列には、外部に公開するすべての関数について PHP で使用する場合の名前と C ソース内で定義されている名前が含まれています。 内部的には、zend_function_entry例46-4 のように定義されています。

例 46-4. 内部での zend_function_entry の宣言

typedef struct _zend_function_entry {
    char *fname;
    void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
    unsigned char *func_arg_types;
} zend_function_entry;

エントリ説明
fname PHP で使用する場合の名前を表します (例えば fopenmysql_connect など。 今回の例では first_module)。
handler この関数コールを処理する C 関数へのポインタ。 例えば、先ほど説明した標準マクロ INTERNAL_FUNCTION_PARAMETERS を参照ください。
func_arg_types パラメータに対して、参照渡しを強制させるようにできます。 通常は NULL を設定しておくべきです。

上の例では、このような宣言になります。
zend_function_entry firstmod_functions[] =
{
    ZEND_FE(first_module, NULL)
    {NULL, NULL, NULL}
};
リストの最後のエントリは、必ず {NULL, NULL, NULL} でなければなりません。 エクスポートされる関数の一覧が終わることを Zend が知るために、 このエントリが必要となります。

注意: 最後を表す印として、定義済みマクロを使用することは できません。そうすると、 "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_entryname という名前の関数エントリを定義します。対応する C の関数が必要です。 arg_typesNULL に設定しなければなりません。 この関数は、自動的に 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_typesNULL に設定しなければなりません。ZEND_FE のように自動的に名前を決められたくない場合にこの関数を使用します。 ZEND_NAMED_FUNCTION と組み合わせて使用します。
ZEND_FALIAS(name, alias, arg_types) alias という名前で、 name のエイリアスを定義します。 arg_typesNULL に設定しなければなりません。対応する C の関数は不要です。 その代わりに、エイリアスの対象を参照します。
PHP_FE(name, arg_types) 古い PHP の API で、ZEND_FE と同じものです。
PHP_NAMED_FE(runtime_name, name, arg_types) 古い PHP の API で、ZEND_NAMED_FE と同じものです。

注意: ZEND_FEPHP_FUNCTION を組み合わせて使用したり、あるいは PHP_FEZEND_FUNCTION を組み合わせて使用したりすることはできません。しかし、 各関数について ZEND_FEZEND_FUNCTION、あるいは PHP_FEPHP_FUNCTION という組み合わせが守られているのなら、 それらを混用することは可能です。 しかし、混用することは 推奨しませんZEND_* マクロだけを使用するようにしましょう。

Zend モジュールブロックの宣言

このブロックは構造体 zend_module_entry に保存され、モジュールの内容について Zend に示すために必要な すべての情報が含まれます。このモジュールの内部定義は 例46-5 で確認できます。

例 46-5. 内部での zend_module_entry の宣言

typedef struct _zend_module_entry zend_module_entry;
     
    struct _zend_module_entry {
    unsigned short size;
    unsigned int zend_api;
    unsigned char zend_debug;
    unsigned char zts;
    char *name;
    zend_function_entry *functions;
    int (*module_startup_func)(INIT_FUNC_ARGS);
    int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    int (*request_startup_func)(INIT_FUNC_ARGS);
    int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
    void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
    char *version;

[ 構造体の残りの部分は、ここではあまり関係がありません ]

};

エントリ説明
size, zend_api, zend_debug および zts 通常は "STANDARD_MODULE_HEADER" を指定します。 これは、4 つのメンバにそれぞれ zend_module_entory 全体のサイズ、ZEND_MODULE_API_NO、 デバッグビルドか通常ビルドのどちらであるか (ZEND_DEBUG) そして ZTS が有効かどうか (USING_ZTS) を代入します。
name モジュール名を指定します (例えば "File functions""Socket functions""Crypt" など)。この名前は、 phpinfo() の "Additional Modules" 欄で使用されます。
functions 先ほど説明した Zend 関数ブロックへのポインタ。
module_startup_func この関数はモジュールの初期化時にコールされ、 最初の一度だけ行う初期化処理 (例えばメモリの確保など) で使用します。初期化に失敗した場合には FAILURE、 成功した場合には SUCCESS を返します。 このフィールドを使用しない場合は、NULL を指定します。関数を宣言するには、マクロ ZEND_MINIT を使用します。
module_shutdown_func この関数はモジュールのシャットダウン時にコールされ、 最後に一度だけ行う後処理 (例えばメモリの開放など) で使用します。 これは module_startup_func() に対応するものです。 で使用します。後処理に失敗した場合には FAILURE、 成功した場合には SUCCESS を返します。 このフィールドを使用しない場合は、NULL を指定します。関数を宣言するには、マクロ ZEND_MSHUTDOWN を使用します。
request_startup_func この関数はページがリクエストされるたびにコールされ、 リクエストを処理する際の前処理で使用します。 処理に失敗した場合には FAILURE、 成功した場合には SUCCESS を返します。 注意: 動的モジュールの場合は リクエストがあるまでは読み込まれないので、 リクエストスタートアップ関数は、 モジュールスタートアップ関数の直後にコールされます (これら二つの初期化イベントが同時に発生します)。 このフィールドを使用しない場合は、NULL を指定します。関数を宣言するには、マクロ ZEND_RINIT を使用します。
request_shutdown_func この関数はページのリクエストが終了するたびにコールされます。 ちょうど request_startup_func() に対応するものです。 処理に失敗した場合には FAILURE、 成功した場合には SUCCESS を返します。 注意: 動的モジュールの場合は リクエストがあるまでは読み込まれないので、 リクエストシャットダウン関数の直後に モジュールシャットダウンハンドラがコールされます (これら二つの後処理イベントが同時に発生します)。 このフィールドを使用しない場合は、NULL を指定します。関数を宣言するには、マクロ ZEND_RSHUTDOWN を使用します。
info_func スクリプト内で phpinfo() がコールされると、 Zend は現在読み込まれているすべてのモジュールについてこの関数をコールします。 つまり、その出力結果に何らかの "あしあと" を残すチャンスが すべてのモジュールに与えられるわけです。一般的には、 これを使用して環境情報や東経情報を出力します。 このフィールドを使用しない場合は、NULL を指定します。関数を宣言するには、マクロ ZEND_MINFO を使用します。
version モジュールのバージョン。バージョン番号をまだつけたくない場合は NO_VERSION_YET が使用できます。しかし、 何らかのバージョン文字列を指定することを推奨します。 バージョン文字列は、例えば次のようなものになります (バージョンの若い順に並べています): "2.5-dev""2.5RC1""2.5" あるいは "2.5pl3"
それ以外の構造体の要素 これらは内部的に使用されるもので、マクロ STANDARD_MODULE_PROPERTIES_EX を使用して事前に設定されます。これらの要素に値を代入してはいけません。 STANDARD_MODULE_PROPERTIES_EX を使用するのは、 グローバルなスタートアップ関数、シャットダウン関数を使用する場合のみです。 それ以外の場合は STANDARD_MODULE_PROPERTIES を直接使用します。

今回の例では、この構造体を次のように実装します。
zend_module_entry firstmod_module_entry =
{
    STANDARD_MODULE_HEADER,
    "First Module",
    firstmod_functions,
    NULL, NULL, NULL, NULL, NULL,
    NO_VERSION_YET,
    STANDARD_MODULE_PROPERTIES,
};
これは、基本的に必要最小限の設定です。モジュール名は First Module とし、関数一覧の参照を設定し、 スタートアップ関数やシャットダウン関数はすべて未使用としています。

参照用として、スタートアップ関数およびシャットダウン関数に関するマクロを 表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 と組み合わせて使用します。

get_module() の作成

これは特別な関数で、すべての動的読み込みモジュールで使用されます。 これを作成するため、まずは 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", &parameter) == FAILURE) {
        return;
    }

    RETURN_LONG(parameter);
}
関数の宣言には ZEND_FUNCTION を使用します。 これは、関数エントリテーブルにおける ZEND_FE に対応しています (こちらについては先ほど説明しました)。

関数宣言の後には、引数のチェックおよびその内容の取得、 引数の変換、そして返り値の作成などのコードが続きます (これらの詳細は後述します)。

まとめ

基本的に、これですべてです。PHP モジュールを構成するのに、 これ以上のものは必要ありません。組み込みのモジュールについても その構造は動的モジュールと同じです。 ここまでに説明した内容を身に着けておけば、今後 PHP モジュールのソースファイルを読んでいく際につまづくこともなくなるでしょう。

さあ、これ以降の節で PHP の内部構造について学び、 強力な拡張モジュールを作っていきましょう。