PHP のコア: Zend Engine ハッカーの手引き
PHP Manual

関数の作成

拡張モジュールの主要な要素のひとつが、 PHP のユーザー空間にエクスポートする関数です。 オブジェクト指向の拡張モジュールを書こうとしている場合でも、 この章を読んでおくことをおすすめします。 ここにある情報の大半はメソッドを書く場合にもあてはまるからです。

ext_skel で新しい基盤を作った後などに新しい関数を追加するには、 その内容を C の関数として実装して、 作った関数のエントリを拡張モジュールの関数テーブルに追加します。 関数のエントリには、引数情報の構造体へのポインタを含めることができます。 パラメータを参照で渡したり参照を返したりするのでない限り、 引数情報を渡すことは必須ではありません。しかし、情報を渡しておけば PHP の Reflection API からアクセスできるようになります。以下の例でわかるとおり、 パラメータはそのまま実装へ渡されるわけではなく スタックに積んで渡されます。 このスタックは関数の実装によってチェックされます。 関数の実装は直接の情報源とはなりません。

例1 関数をひとつ持つ最小限の PHP 拡張モジュール

/* {{{ proto void hello_world()
       Do nothing */
PHP_FUNCTION(hello_world)
{
}
/* }}} */

/* {{{ arginfo_hello_world */
ZEND_BEGIN_ARG_INFO(arginfo_hello_world, 0)
ZEND_END_ARG_INFO()
/* }}} */

/* {{{ demo_functions */
function_entry demo_functions[] = {
    PHP_FE(hello_world, arginfo_hello_world)
    {NULL, NULL, NULL}
}
/* }}} */

/* {{{ demo_module_enry */
zend_module_entry demo_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    "demo",
    demo_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    "1.0.0",
#en
    STANDARD_MODULE_PROPERTIES
}
/* }}} */

この例では、上で説明した要素やモジュールの構造を確認することができます。 この構造に見覚えがない場合は zend_module 構造体 を見直してみましょう。

この拡張モジュールの最初の部分は、実際の実装です。 慣習として、エクスポートする関数の前には二行のコメントをつけることになっています。 コメントには、利用者向けの関数プロトタイプと 一行での短い説明を記述します。

PHP の異なるバージョン間でソースコードの互換性を保持するため、 関数の宣言を PHP_FUNCTION マクロでラップしています。 PHP 5.3 を使っている場合、コンパイラのプリプロセッサは これを次のような関数に変換します。

void zif_hello_world(int ht, zval *return_value, zval **return_value_ptr,
                     zval *this_ptr, int return_value_used TSRMLS_DC)
{
}

ユーザー空間にエクスポートする関数や C の別の関数との名前の衝突を避けるため、 エクスポートする関数の C のシンボルには先頭に zif_ を付け加えます。 また、このプロトタイプでは引数スタックを参照していないことにもお気づきでしょう。 PHP から渡されたパラメータへのアクセス方法については後で説明します。 この表は、C レベルでのパラメータをまとめたものです。これらは INTERNAL_FUNCTION_PARAMETERS マクロでも定義されています。 これらのパラメータは、PHP のバージョンが変われば変化する可能性があることに注意しましょう。 提供されているマクロ経由で使わなければなりません。

INTERNAL_FUNCTION_PARAMETERS
名前と型 説明 アクセス用のマクロ
int ht ユーザーから渡された実際のパラメータの数 ZEND_NUM_ARGS()
zval *return_value PHP 変数へのポインタ。ユーザーに返す値をここに格納します。 デフォルトの型は IS_NULL です。 RETVAL_*, RETURN_*
zval **return_value_ptr PHP に参照を返す場合は、ここに変数へのポインタを設定します。 参照を返すことは推奨しません。  
zval *this_ptr メソッドコールの場合に、これは $this オブジェクトを保持する PHP の変数を指します。 getThis()
int return_value_used 返す値を呼び出し元で使うかどうかを示すフラグ。  

先ほども説明したように、上の関数は単に NULL を返すだけで他に何もしません。 PHP 側からこの関数をコールするときには任意の数のパラメータを指定することができます。 もう少し実用的な関数は、大きく四つの部分にわけることができます。

  1. ローカル変数の宣言。C ではローカル変数の宣言が必要です。 これは関数の先頭で行います。

  2. パラメータのパース。 PHP はパラメータを特別なスタックに積んで渡すので、 そこからパラメータを取り出して読み込み、型を検証し、 必要に応じてキャストし、何か問題があれば回避します。

  3. 実際のロジック。必要な処理を書きます。

  4. 返り値の設定、後始末。そして結果を返します。

場合によってはこれらの順序は前後することもあります。 特に最後のふたつは一緒になってしまうことも多いでしょう。 しかし、この順序を守っておくほうがよいでしょう。

例2 単純な関数

/* {{{ proto void hello_world(string name)
   Greets a user */
PHP_FUNCTION(hello_world)
{
    char *name;
    int name_len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
        return;
    }

    php_printf("Hello %s!", name);

    RETURN_TRUE;
}
/* }}} */

この関数は、先ほどの各部分を示したものです。 まずは最後の行から見ていきましょう。php_printf は、容易に想像できるとは思いますが、標準 C の関数 printf の PHP 版です。printf とは異なり、 この関数はプロセスの STDOUT には出力せず 現在の出力ストリームに表示します。出力ストリームはユーザー側でバッファリングされることもあります。 PHP の API のほとんどはバイナリセーフではありません。name に NULL バイトが含まれている場合、php_printf はそれ以降を無視してしまうことに注意しましょう。 バイナリセーフな出力には PHPWRITE を使わなければなりません。

注意: 一般に、データを直接出力ストリームに送るよりはユーザーに文字列として返すことを推奨します。 そうすれば、ユーザー側で何処に出力するかを決めさせることができます。 この方針の例外は、画像のようなバイナリデータを扱うときです。 そのようなデータを扱うための方法を API で用意しておく必要があります。

最後の行にあるマクロ RETURN_TRUE は三つのことを行います。 return_value ポインタで取得した変数の型を IS_BOOLEAN に設定し、その値を true として、 C の関数から処理を返します。 つまり、このマクロを使うとメモリやその他のリソースの後始末も完了し、 関数内のそれ以降のコードは実行されません。

zend_parse_parameters() 関数の役割は、 ユーザーが渡したパラメータを引数スタックから読み込んで 適切なキャストを施してローカルの C 変数に格納することです。 ユーザーが渡すパラメータの数が間違っていたりキャストに失敗したりした場合は、 エラーを発生させて FAILURE を返します。 この場合は何もせずに関数から抜けます。return_value は手を加えずそのままで、ユーザーへの返り値はデフォルトの NULL となります。

注意: FAILURE-1、そして SUCCESS0 であることを覚えておきましょう。 コードの可読性を考慮して、値を比較するときは常にこの定数をつかうべきです。

zend_parse_parameters() の最初のパラメータは、 ユーザーからその関数に渡された実際のパラメータの数です。 この数は ht パラメータで渡されていますが、 先に説明したように、今の実装ではそうなっているというだけに過ぎません。 ここでは ZEND_NUM_ARGS() を使わなければなりません。

PHP のスレッドセーフなリソースマネージャーとの互換性を考慮して、 TSRMLS_CC でスレッドコンテキストも渡す必要があります。 他の関数とは異なり、これは最後のパラメータとはなりません。 zend_parse_parameters は可変個数のパラメータを受け付け、 それは読み込むユーザーパラメータの数に依存するからです。

スレッドコンテキストの後には、想定しているパラメータを宣言します。 各パラメータが文字列内の一文字として表され、それが型を示します。 この例では文字列のパラメータを想定しているので、型の指定は単純に "s" となります。

最後に渡すのは一つあるいは複数の C 変数へのポインタで、 ここには変数の値やその他の情報が格納されます。 今回のような文字列の場合は、 NULL 終端の実際の文字列を表す char* と NULL バイトを含む文字列の長さを表す int になります。

すべての型指定子とそれに対応する C の型については、ソース配布物の中にある » README.PARAMETER_PARSING_API に説明があります。 中でも重要な型について次の表にまとめました。

zend_parse_parameters() の型指定子
修飾子 パラメータの型 説明
b zend_bool Boolean 値
l long integer (long) 値
d double float (double) 値
s char*, int バイナリセーフな文字列
h HashTable* 配列のハッシュテーブル

PHP のコア: Zend Engine ハッカーの手引き
PHP Manual