PHP マニュアル
前のページ | 第 46章Zend API: PHP のコアをハックする | 次のページ

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

さあ、今やあなたは安全なビルド環境を手にいれ、 モジュールを 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 );

`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 に登録しなければなりません。関数のリストを登録するには、 `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 で使用する場合の名前を表します (例えば `fopen` や `mysql_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_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 モジュールブロックの宣言

このブロックは構造体 `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 の内部構造について学び、 強力な拡張モジュールを作っていきましょう。

前のページ | ホーム | 次のページ
トラブルシューティング | 上に戻る | 引数の扱い