2006年10月30日
PHP Extension を作ろう第2回 - 引数と返値
前回の Hello World のサンプルプログラムで一通りの PHP Extension の作成手順を見てきました。しかし helloworld() の様に引数も返値も無い関数だけではプログラミング言語として不便ですので今回は PHP と PHP Extension におけるデータタイプの詳細と引数、返値の渡し方について見ていきましょう。
* zval とは
PHP のコードから C言語で書かれたコードへとデータをやり取りする上で「型」についての疑問が浮かび上がります。動的形付けされた PHP の変数を静的に形付けされた C言語で扱うにはどうしたらよいのでしょうか。
まずは、PHP 内部でのデータ表現を知るために PHP のソースを覗いて見ましょう。
php-x.x.x/Zend/zend.h より
上記の構造体を眺めて見るとピンと来る方もいらっしゃると思いますが。PHP で登場するあらゆる型(文字列型、整数型、論理型、配列型など)の変数は上記の構造体で表現できます。
Extension とのデータの受け渡しも上記の構造体が使用されますが、便利なマクロが用意されていて直接アクセスする機会は少ないと思いますので覚える必要性は無いでしょう。
* 引数の渡し方
PHP から渡された変数を Extension で使用するには zval 構造体から C言語の型に変換を行います、それには型の種類にかかわらず zend_parse_parameters() 関数を使用すると便利です。この関数は全ての型を扱えるためか若干ややこしいので、具体的な用例から見ていきましょう。
以下の例は、関数へ渡された PHP の整数型を C言語の long 形へ変換する例です。
見慣れないマクロが目に付くと思いますが、注目すべき点は PHP の整数型で
ある引数を long 型である value に納めている点です。
以下の PHP プログラムを実行すると
実行結果は
となります。
次に、PHP の文字列型を C言語の char配列に変換してみましょう。
上記は、引数で渡した文字列をそのまま表示する関数ですが、整数の時と比べると文字列の長さを得ているという違いがあります。
str が NULL 終端しているのであれば、長さは必要ないのでは?と気が付く方がいらっしゃると思いますが、PHP の文字列にはバイナリデータが格納できるので文字列の長さも一緒に渡されます。
整数型と文字列型の渡し方を見てきましたが zend_parse_parameters() の第2引数 "l" や "s" は対象の型を示します。関数に複数の引数を渡す場合 "lllss" というように連結します。
以下に zend_parse_parameters() の第2引数に渡す文字と、型の一覧を示します。
* 返値の返し方
関数から返値を返す為に以下のマクロが用意されています。
これらのマクロを呼び出しても プリプロセスした C言語のコードではすべて
return; (返値の型は void)
であることが解ります。
実は C言語のレベルでは 引数で渡された zval の参照で値を返しているのです。
最後に、これまでの内容を振り返り、具体的な用例を見ていきましょう。
* 例1
** 2つの整数を引数として受け取り商を求める example_div() 関数
** PHP のコード例
** 実行例
# ゼロ除算を起こしてみよう
* 例2
** 2つの文字列を受け取り、一致すれば true を返す example_strcmp() 関数
** PHP のコード例
**実行例
* 例3
** 整数の配列の総和を求める example_sum() 関数
** PHP のコード例
** 実行結果
配列にアクセスする為に見慣れないマクロが多く登場してしまいましたがこれらの詳細については PHP のドキュメントをご覧下さい。と言いたい所なのですが現状ではこの辺りのドキュメントは十分無く、一番のドキュメントと呼べるのは PHP のソースコード、または 既存の PECL モジュールが参考になると思います。
次回は、PHP Extension でのメモリの操作を見ていきたいと思います
PHP のコードから C言語で書かれたコードへとデータをやり取りする上で「型」についての疑問が浮かび上がります。動的形付けされた PHP の変数を静的に形付けされた C言語で扱うにはどうしたらよいのでしょうか。
まずは、PHP 内部でのデータ表現を知るために PHP のソースを覗いて見ましょう。
php-x.x.x/Zend/zend.h より
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object obj;
} zvalue_value;
struct _zval_struct {
zvalue_value value; /* value */
zend_uchar type; /* active type */
zend_uchar is_ref;
zend_ushort refcount;
};
上記の構造体を眺めて見るとピンと来る方もいらっしゃると思いますが。PHP で登場するあらゆる型(文字列型、整数型、論理型、配列型など)の変数は上記の構造体で表現できます。
Extension とのデータの受け渡しも上記の構造体が使用されますが、便利なマクロが用意されていて直接アクセスする機会は少ないと思いますので覚える必要性は無いでしょう。
* 引数の渡し方
PHP から渡された変数を Extension で使用するには zval 構造体から C言語の型に変換を行います、それには型の種類にかかわらず zend_parse_parameters() 関数を使用すると便利です。この関数は全ての型を扱えるためか若干ややこしいので、具体的な用例から見ていきましょう。
以下の例は、関数へ渡された PHP の整数型を C言語の long 形へ変換する例です。
PHP_FUNCTION(example1)
{
long value;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &value) == FAILURE)
return;
printf("this is long value: %ld\n", value);
return;
}
見慣れないマクロが目に付くと思いますが、注目すべき点は PHP の整数型で
ある引数を long 型である value に納めている点です。
以下の PHP プログラムを実行すると
example1(10);
実行結果は
this is long value: 10
となります。
次に、PHP の文字列型を C言語の char配列に変換してみましょう。
PHP_FUNCTION(example2)
{
char *str;
int str_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"s", &str, &str_len) == FAILURE) {
return;
}
printf("%s\n", str);
}
上記は、引数で渡した文字列をそのまま表示する関数ですが、整数の時と比べると文字列の長さを得ているという違いがあります。
str が NULL 終端しているのであれば、長さは必要ないのでは?と気が付く方がいらっしゃると思いますが、PHP の文字列にはバイナリデータが格納できるので文字列の長さも一緒に渡されます。
整数型と文字列型の渡し方を見てきましたが zend_parse_parameters() の第2引数 "l" や "s" は対象の型を示します。関数に複数の引数を渡す場合 "lllss" というように連結します。
以下に zend_parse_parameters() の第2引数に渡す文字と、型の一覧を示します。
l long 整数型
d double 浮動小数点数型
s string 文字列型
b boolean 論理値型
r resource リソース型
a array 配列型
o any object オブジェクト型
z zval zval型
* 返値の返し方
関数から返値を返す為に以下のマクロが用意されています。
RETURN_RESOURCE(l)
RETURN_BOOL(b)
RETURN_NULL()
RETURN_LONG(l)
RETURN_DOUBLE(d)
RETURN_STRING(s, dup)
RETURN_STRINGL(s, l, dup)
RETURN_EMPTY_STRING()
RETURN_FALSE
RETURN_TRUE
これらのマクロを呼び出しても プリプロセスした C言語のコードではすべて
return; (返値の型は void)
であることが解ります。
実は C言語のレベルでは 引数で渡された zval の参照で値を返しているのです。
最後に、これまでの内容を振り返り、具体的な用例を見ていきましょう。
* 例1
** 2つの整数を引数として受け取り商を求める example_div() 関数
PHP_FUNCTION(example_div)
{
long l1, l2;
double d;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"ll", &l1, &l2) == FAILURE) {
return;
}
d = l1 / l2;
RETURN_DOUBLE(d);
}
** PHP のコード例
echo example_div(10, 2) . "\n";
** 実行例
5
# ゼロ除算を起こしてみよう
* 例2
** 2つの文字列を受け取り、一致すれば true を返す example_strcmp() 関数
PHP_FUNCTION(example_strcmp)
{
int i;
char *str1, *str2;
int str1_len, str2_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
&str1, &str1_len, &str2, &str2_len) == FAILURE) {
return;
}
if(str1_len != str2_len) RETURN_FALSE;
for(i=0; i<str1_len; i++){
if(str1[i] != str2[i]) RETURN_FALSE;
}
RETURN_TRUE;
}
** PHP のコード例
if(example_strcmp("test", "test")){
print("same string\n");
}else{
print("different string\n");
}
**実行例
same string
* 例3
** 整数の配列の総和を求める example_sum() 関数
PHP_FUNCTION(example_sum)
{
zval *a;
int i, num;
long sum = 0;
HashTable *hash;
zval **data;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"a", &a) == FAILURE){
return;
}
hash = Z_ARRVAL_P(a);
num = zend_hash_num_elements(hash); // 配列の要素数を得る
for(i=0; i<num; i++){
if(zend_hash_get_current_data(hash, (void **)&data) == FAILURE) {
return;
}
if (Z_TYPE_PP(data) != IS_LONG) {
return; // 配列の要素が整数で無いため中断する
}
sum += Z_LVAL_PP(data);
zend_hash_move_forward(hash);
}
RETURN_DOUBLE(sum);
}
** PHP のコード例
printf("sum=%ld\n", example_sum(array(1, 2, 3, 4, 5)));
** 実行結果
sum=15
配列にアクセスする為に見慣れないマクロが多く登場してしまいましたがこれらの詳細については PHP のドキュメントをご覧下さい。と言いたい所なのですが現状ではこの辺りのドキュメントは十分無く、一番のドキュメントと呼べるのは PHP のソースコード、または 既存の PECL モジュールが参考になると思います。
次回は、PHP Extension でのメモリの操作を見ていきたいと思います