関数を引数に取る方法
PHPの関数を引数にすることが可能になると、エクステンションの作成の幅も広がると思います。なぜなら、PHPでできることとエクステンション側でできることについて、より密接な連携が実現するようになるからです。PHPで任意の関数といえばcall_user_func()という関数がありますので、そのコードを参照してみます。
PHP_FUNCTION(call_user_func) { zval *retval_ptr = NULL; zend_fcall_info fci; // ……(1) zend_fcall_info_cache fci_cache; // ……(2) if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "f*", &fci, &fci_cache, &fci.params, &fci.param_count) == FAILURE) { // ……(3) return; } fci.retval_ptr_ptr = &retval_ptr; if (zend_call_function(&fci, &fci_cache TSRMLS_CC) == SUCCESS && fci.retval_ptr_ptr && *fci.retval_ptr_ptr) { // ……(4) COPY_PZVAL_TO_ZVAL(*return_value, *fci.retval_ptr_ptr); // ……(5) } if (fci.params) { efree(fci.params); // ……(6) } }
関数はzend_fcall_infoとzend_fcall_info_cacheの構造体に設定されますので、(1)、(2)のように宣言します。(3)のようにzend_parse_parameters()には"f"の引数を渡します。ただし、今回は実行される関数の引数が可変引数になるので、"f*"になります。実際の関数の実行は、(4)のzend_call_function()で行えます。次に(5)のように実行した結果をreturn_valueの変数に設定します。最後に(6)のようにメモリの解放を行う必要があります。
エクステンション側でフレームワークのようなものを作る場合には、PHPの関数をハンドラとして登録すれば実現できます。このような実装はxml_set_element_handler-で行っていますので、これらのソースを調べてみると、より応用範囲も広がると思います。
複数の引数を扱う方法
これまでの例では引数は必ず1つのみでしたが、続いて複数の引数を扱う方法を紹介します。とはいっても、ここまでの使い方を知っていれば、下記の例を見れば簡単に予測がつくかと思いますので、簡単な紹介にとどめます。
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &val1,&val2) // ……(1) zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|l", &val1,&val2) // ……(2)
以下の2つの方法はいずれも2つのlong型の引数を扱う方法です。しかし、(2)は"|"(パイプ)で区切られています。分かりやすいように、これらの違いをPHPのコードで示します。
// (1)の時の引数 function func_1($l1,$l2){ } // (2)の時の引数 function func_2($l1,$2 = null){ }
つまり、(1)の場合は必ず2個の引数を求めていて、(2)の場合には、2つめの引数は省略可能であることを示しています。
可変引数を取る関数を定義する方法
ほとんどの場合は、引数が複数であってもただ該当する文字を並べていけばよいだけですが、PHPのprintf()関数のように引数の数を可変にした関数を作成したい場合があります。配列で引数を指定しても機能上は変わりありませんが、可変引数型の関数の方がソースコード上の参照しやすさが変わってきます。ただし、可変部分については引数の型を指定できませんので、配列を引数にする場合と同様に実際には型を調べて処理をすることになると思います。
可変引数の場合のzend_parse_parameters()の記述方法
実際に可変引数を実装しているprintf()のソースコードの該当部分を見てみると以下のようになっています。
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "+", &args, &argc)
これは1個以上の引数を扱う場合で、"+"を引数に指定します。続いて、引数が0個以上の場合のケースとしてsscanf()関数の場合を紹介します。
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss*", &str, &str_len, &format, &format_len,&args, &num_args)
単純に"*"のように指定しているのではなく"ss*"と定義しています。実際にsscanf()関数のマニュアルでの定義を参照してみると、以下のようになっています。
mixed sscanf ( string $str , string $format [, mixed &$... ] )
つまり、2つの文字列の後に0個以上の可変引数を指定しています。このように今までの型指定の後に指定することも可能です。
可変引数を使った場合の注意点
可変引数を使って関数を定義する場合に注意しなければならないことがあります。それは、戻り値を設定する場合にRETURN_*マクロを使えず、必ずRETVAL_*を使う必要があります。これは必ず、メモリの解放が必要になるため戻り値の設定後に、後処理をする必要があるためです。実際にsscanfの実装部分をすべて参照してみます。
PHP_FUNCTION(sscanf) { zval ***args = NULL; char *str, *format; int str_len, format_len, result, num_args = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss*", &str, &str_len, &format, &format_len, &args, &num_args) == FAILURE) { return; } result = php_sscanf_internal(str, format, num_args, args, 0, &return_value TSRMLS_CC); if (args) { efree(args); // ……(1) } if (SCAN_ERROR_WRONG_PARAM_COUNT == result) { WRONG_PARAM_COUNT; } }
(1)のように、実際の引数の値を保持する変数のメモリを解放する必要があります。