PHP エクステンションから戻り値を返す方法

PHP エクステンションでの戻り値の扱い方

PHP エクステンションの関数から、呼び出し元の PHP スクリプトへ戻り値を返したい場合について説明します。

PHP エクステンションでは、事前割当てされた return_value という zval 値に値をセットすることによって戻り値を返します

return_value ってどこにあるの?

PHP_FUNCTION は ZEND_FUNCTION の別名なのですが、ZEND_FUNCTION は次のように定義されています。

#define ZEND_FUNCTION(name)		ZEND_NAMED_FUNCTION(ZEND_FN(name)) 

さらに掘り下げると、ZEND_FN と ZEND_NAMED_FUNCTION は次のようになります。

#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name)		void name(INTERNAL_FUNCTION_PARAMETERS)

さらに INTERNAL_FUNCTION_PARAMETERS は次の通りです。

#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, 
     zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

すなわち、PHP_FUNCTION(foo) は ZEND_FUNCTION(foo) と同じ。 ZEND_FUNCTION(foo) は ZEND_NAMED_FUNCTION(ZEND_FN(foo)) となり、 これは ZEND_NAMED_FUNCTION(zif_foo) となる。この結果 PHP_FUNCTION(foo) は、
void zif_foo(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC)
となり、確かに return_value が渡されていることがわかります。

実際の例で考えると、単純な PHP エクステンションの開発 で作成した php_keicode1.dll の keicode_test 関数は、以下のように zif_keicode_test となっていることがわかります。

> dumpbin /ALL php_keicode1.dll

とすると、?zif_keicode_test@@YAXHPAU_zval_struct@@PAPAU1@0HPAPAPAX@Z というシンボルが見えますが、 これは名前の非修飾をすると、次のような関数であることが確認できます。

> undname ?zif_keicode_test@@YAXHPAU_zval_struct@@PAPAU1@0HPAPAPAX@Z
Microsoft (R) C++ Name Undecorator
Copyright (C) Microsoft Corporation. All rights reserved.

Undecoration of :- "?zif_keicode_test@@YAXHPAU_zval_struct@@PAPAU1@0HPAPAPAX@Z"
is :- "void __cdecl zif_keicode_test(int,struct _zval_struct *,struct _zval_struct * *,
  struct _zval_struct *,int,void * * *)"

zval とはどのような型なのか?

さて、zval というのはどのような形をしているのでしょうか。

zval は zend.h で定義されている次の構造体です。

struct _zval_struct {
	zvalue_value value;
	zend_uint refcount;
	zend_uchar type;
	zend_uchar is_ref;
};

zvalue_value は次の型です。

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_value obj;
} zvalue_value;

値の返し方 ~ zval 型の return_value のセット方法

ポイントは、zval の value と type です。これらそれぞれに、値、型情報を適切に値をセットしてあげればいいことになります。

型情報は次のように定義されている値を使います。

#define IS_NULL		0
#define IS_LONG		1
#define IS_DOUBLE	2
#define IS_BOOL		3
#define IS_ARRAY	4
#define IS_OBJECT	5
#define IS_STRING	6
#define IS_RESOURCE	7
#define IS_CONSTANT	8
#define IS_CONSTANT_ARRAY	9

例えば、zval 型の z に整数の 123 をセットしたい場合は、z.value.lval = 123, z.type = IS_LONG という風にセットすればよいことになります。 また return_value のようにポインターで渡されるものについては、z->value.lval = 123, z->type = IS_LONG です。

しかしながら、値を返すというだけで毎回この調子ではあまりにも面倒くさいので、各種マクロが用意されています。

例えば値 123 を返すには、return_value->value.lval = 123, return_value->type = IS_LONG とする代わりに次のようにします。

Z_LVAL_P(return_value) = 123;
Z_TYPE_P(return_value) = IS_LONG;

Z_LVAL_P、Z_TYPE_P は zend_operators.h で定義されているマクロです。

他にも以下のように定義されています。

#define Z_LVAL(zval)               (zval).value.lval
#define Z_BVAL(zval)               ((zend_bool)(zval).value.lval)
#define Z_DVAL(zval)               (zval).value.dval
#define Z_STRVAL(zval)               (zval).value.str.val
#define Z_STRLEN(zval)               (zval).value.str.len
#define Z_ARRVAL(zval)               (zval).value.ht
#define Z_OBJVAL(zval)               (zval).value.obj
#define Z_OBJ_HANDLE(zval)          Z_OBJVAL(zval).handle
#define Z_OBJ_HT(zval)               Z_OBJVAL(zval).handlers
#define Z_OBJCE(zval)               zend_get_class_entry(&(zval) TSRMLS_CC)
#define Z_OBJPROP(zval)               Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC)
#define Z_OBJ_HANDLER(zval, hf) Z_OBJ_HT((zval))->hf
#define Z_RESVAL(zval)               (zval).value.lval

#define Z_LVAL_P(zval_p)          Z_LVAL(*zval_p)
#define Z_BVAL_P(zval_p)          Z_BVAL(*zval_p)
#define Z_DVAL_P(zval_p)          Z_DVAL(*zval_p)
#define Z_STRVAL_P(zval_p)          Z_STRVAL(*zval_p)
#define Z_STRLEN_P(zval_p)          Z_STRLEN(*zval_p)
#define Z_ARRVAL_P(zval_p)          Z_ARRVAL(*zval_p)
#define Z_OBJPROP_P(zval_p)          Z_OBJPROP(*zval_p)
#define Z_OBJCE_P(zval_p)          Z_OBJCE(*zval_p)
#define Z_RESVAL_P(zval_p)          Z_RESVAL(*zval_p)
#define Z_OBJVAL_P(zval_p)      Z_OBJVAL(*zval_p)
#define Z_OBJ_HANDLE_P(zval_p)  Z_OBJ_HANDLE(*zval_p)
#define Z_OBJ_HT_P(zval_p)      Z_OBJ_HT(*zval_p)
#define Z_OBJ_HANDLER_P(zval_p, h) Z_OBJ_HANDLER(*zval_p, h)

#define Z_LVAL_PP(zval_pp)          Z_LVAL(**zval_pp)
#define Z_BVAL_PP(zval_pp)          Z_BVAL(**zval_pp)
#define Z_DVAL_PP(zval_pp)          Z_DVAL(**zval_pp)
#define Z_STRVAL_PP(zval_pp)     Z_STRVAL(**zval_pp)
#define Z_STRLEN_PP(zval_pp)     Z_STRLEN(**zval_pp)
#define Z_ARRVAL_PP(zval_pp)     Z_ARRVAL(**zval_pp)
#define Z_OBJPROP_PP(zval_pp)     Z_OBJPROP(**zval_pp)
#define Z_OBJCE_PP(zval_pp)          Z_OBJCE(**zval_pp)
#define Z_RESVAL_PP(zval_pp)     Z_RESVAL(**zval_pp)
#define Z_OBJVAL_PP(zval_pp)    Z_OBJVAL(**zval_pp)
#define Z_OBJ_HANDLE_PP(zval_p) Z_OBJ_HANDLE(**zval_p)
#define Z_OBJ_HT_PP(zval_p)     Z_OBJ_HT(**zval_p)
#define Z_OBJ_HANDLER_PP(zval_p, h) Z_OBJ_HANDLER(**zval_p, h)

#define Z_TYPE(zval)          (zval).type
#define Z_TYPE_P(zval_p)     Z_TYPE(*zval_p)
#define Z_TYPE_PP(zval_pp)     Z_TYPE(**zval_pp)

さらに、return_value という変数名専用に、次のようなマクロもあります。

#define RETVAL_RESOURCE(l)                    ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b)                         ZVAL_BOOL(return_value, b)
#define RETVAL_NULL()                          ZVAL_NULL(return_value)
#define RETVAL_LONG(l)                          ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d)                     ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate)           ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate)      ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING()                ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor)          ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE                           ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE                            ZVAL_BOOL(return_value, 1)

#define RETURN_RESOURCE(l)                     { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b)                          { RETVAL_BOOL(b); return; }
#define RETURN_NULL()                          { RETVAL_NULL(); return;}
#define RETURN_LONG(l)                          { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d)                     { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate)      { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING()                { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor)          { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE                           { RETVAL_FALSE; return; }
#define RETURN_TRUE                            { RETVAL_TRUE; return; }

#define ZVAL_RESOURCE(z, l) {          \
          Z_TYPE_P(z) = IS_RESOURCE;     \
          Z_LVAL_P(z) = l;               \
     }

#define ZVAL_BOOL(z, b) {               \
          Z_TYPE_P(z) = IS_BOOL;          \
          Z_LVAL_P(z) = ((b) != 0);   \
     }

#define ZVAL_NULL(z) {                    \
          Z_TYPE_P(z) = IS_NULL;          \
     }

#define ZVAL_LONG(z, l) {               \
          Z_TYPE_P(z) = IS_LONG;          \
          Z_LVAL_P(z) = l;               \
     }

#define ZVAL_DOUBLE(z, d) {               \
          Z_TYPE_P(z) = IS_DOUBLE;     \
          Z_DVAL_P(z) = d;               \
     }

#define ZVAL_STRING(z, s, duplicate) {     \
          char *__s=(s);                         \
          (z)->value.str.len = strlen(__s);     \
          (z)->value.str.val = (duplicate?estrndup(__s, (z)->value.str.len):__s);     \
          (z)->type = IS_STRING;             \
     }

#define ZVAL_STRINGL(z, s, l, duplicate) {     \
          char *__s=(s); int __l=l;          \
          (z)->value.str.len = __l;         \
          (z)->value.str.val = (duplicate?estrndup(__s, __l):__s);     \
          (z)->type = IS_STRING;              \
     }

#define ZVAL_EMPTY_STRING(z) {             \
          (z)->value.str.len = 0;           \
          (z)->value.str.val = STR_EMPTY_ALLOC(); \
          (z)->type = IS_STRING;              \
     }

#define ZVAL_ZVAL(z, zv, copy, dtor) {  \
          int is_ref, refcount;           \
          is_ref = (z)->is_ref;           \
          refcount = (z)->refcount;       \
          *(z) = *(zv);                   \
          if (copy) {                     \
               zval_copy_ctor(z);          \
         }                               \
          if (dtor) {                     \
               if (!copy) {                \
                    ZVAL_NULL(zv);          \
               }                           \
               zval_ptr_dtor(&zv);         \
         }                               \
          (z)->is_ref = is_ref;           \
          (z)->refcount = refcount;       \
     }

これらのマクロを利用すると、先ほど例として取り上げた次のコード...

Z_LVAL_P(return_value) = 123;
Z_TYPE_P(return_value) = IS_LONG;

この部分は次のように書くことができます。

RETVAL_LONG(123);

さらに、戻り値をセットして関数から抜けるときには、次のようにかけます。

RETURN_LONG(123);

単純な PHP エクステンションの開発方法 のなかでは、文字列を返していますが次のようなコードでした。

PHP_FUNCTION(keicode_test) {

     RETURN_STRING( "Hello World!", 1);

}

配列を返す場合

配列を返す場合は次のようにします。

まず、array_init 関数を使って return_value を初期化します。次に

例として次のコードを見てみましょう。

PHP_FUNCTION(keicode_retval1) {

     array_init(return_value);
     
     add_index_long(return_value, 0, 123);
     add_index_string(return_value, 1, "AAA", 1);
     add_next_index_long(return_value, 999);
     add_next_index_string(return_value, "BBB", 1);

}

このコードでは戻り値となる return_value を array_init で初期化した後、long, string の値を計4つ追加しています。

この状態で次の PHP スクリプトを実行します。

<?php

$ret = keicode_retval1();

foreach( $ret as $x ) {
     echo "$x\n";
}

var_dump($ret);

?>

すると、次のような結果を得られました。

> php test.php
123
AAA
999
BBB
array(4) {
  [0]=>
  int(123)
  [1]=>
  string(3) "AAA"
  [2]=>
  int(999)
  [3]=>
  string(3) "BBB"
}

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説