PHP扩展开发及内核应用

6.1 函数返回值

你也许会认为扩展中定义的函数应该直接通过return关键字来返回一个值,比如由你自己来生成一个zval并返回,就像下面这样:

ZEND_FUNCTION(sample_long_wrong)
{
    zval *retval;

    MAKE_STD_ZVAL(retval);
    ZVAL_LONG(retval, 42);

    return retval;
}

但是,上面的写法是无效的!与其让扩展开发员每次都初始化一个zval并return之,zend引擎早就准备好了一个更好的方法。它在每个zif函数声明里加了一个zval*类型的形参,名为return_value,专门来解决返回值这个问题。在前面我们已经知道了ZEND_FUNCTION宏展开后是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
  • int ht
  • zval *return_value,我们在函数内部修改这个指针,函数执行完成后,内核将把这个指针指向的zval返回给用户端的函数调用者。
  • zval **return_value_ptr,
  • zval *this_ptr,如果此函数是一个类的方法,那么这个指针的含义和PHP语言中$this变量差不多。
  • int return_value_used,代表用户端在调用此函数时有没有使用到它的返回值。
下面让我们先试验一个非常简单的例子,我先给出PHP语言中的实现,然后给出我们在扩展中用C语言完成相同功能的代码。 ````php ```` 下面是我们在编写扩展时的实现。 ````c ZEND_FUNCTION(sample_long) { ZVAL_LONG(return_value, 42); return; } ```` 需要注意的是,ZEND_FUNCTION本身并没有通过return关键字返回任何有价值的东西,它只不过是在运行时修改了return_value指针所指向的变量的值而已,而内核则会把return_value指向的变量作为用户端调用此函数后的得到的返回值。回想一下,ZVAL_LONG()宏是对一类操作的封装,展开后应该就是下面这样: ````c Z_TYPE_P(return_value) = IS_LONG; Z_LVAL_P(return_value) = 42; //更彻底的讲,应该是这样的: return_value->type = IS_LONG; return_value->value.lval = 42; ````
我们千万不要自己去修改return_value的is_ref__gc和refcount__gc属性,这两个属性的值会由PHP内核自动管理。
现在我们把它加到我们在第五章得到的那个扩展框架里,并把这个函数名称注册到函数入口数组里,就像下面这样: ````c static zend_function_entry walu_functions[] = { ZEND_FE(walu_hello, NULL) PHP_FE(sample_long, NULL) { NULL, NULL, NULL } }; ```` 现在我们编译我们的扩展,便可以在用户端通过调用sample_long函数来得到一个整型的返回值了: ````php ```` ### 与return_value有关的宏 return_value如此重要,内核肯定早已经为它准备了大量的宏,来简化我们的操作,提高程序的质量。 在前几章我们接触的宏大多都是以ZVAL_开头的,而接下来我们要介绍的宏的名字是:RETVAL。 再回到上面的那个例子,我们用RETVAL来重写一下: ````c PHP_FUNCTION(sample_long) { RETVAL_LONG(42); //展开后相当与ZVAL_LONG(return_value, 42); return; } ```` 大多数情况下,我们在处理完return_value后所做的便是用return语句结束我们的函数执行,帮人帮到底,送佛送到西,为了减少我们的工作量,内核中还提供了RETURN_*系列宏来为我们自动补上return;如: PHP_FUNCTION(sample_long) { RETURN_LONG(42); //#define RETURN_LONG(l) { RETVAL_LONG(l); return; } php_printf("I will never be reached.\n"); //这一行代码永远不会被执行。 } 下面,我们给出目前所有的RETVAL_***宏和RETURN_***宏,供大家查阅使用。 ````c //这些宏都定义在Zend/zend_API.h文件里 #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; } ```` 其实,除了这些标量类型,还有很多php语言中的复合类型我们需要在函数中返回,如数组和对象,我们可以通过RETVAL_ZVAL与RETURN_ZVAL来操作它们,有关它们的详细介绍我们将在后续章节中叙述。 ### 不返回值可以么? 其实,zend internal function的形参中还有一个比较常用的名为return_value_used的参数,它是干嘛使的呢?它用来标志这个函数的返回值在用户端有没有用到。看下面的代码: ````php 在上面的代码中,$b其实是$a的一个引用,当最后一行代码执行后,$a和$b都开始寻找‘bar’这个字符串对应的zval,让我们以内核的角度重新观察这一切: #if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) ZEND_FUNCTION(return_by_ref) { zval **a_ptr; zval *a; //检查全局作用域中是否有$a这个变量,如果没有则添加一个 //在内核中真的是可以胡作非为啊,:-) if(zend_hash_find(&EG(symbol_table) , "a",sizeof("a"),(void **)&a_ptr ) == SUCCESS ) { a = *a_ptr; } else { ALLOC_INIT_ZVAL(a); zend_hash_add(&EG(symbol_table), "a", sizeof("a"), &a,sizeof(zval*), NULL); } //废弃return_value,使用return_value_ptr来接替它的工作 zval_ptr_dtor(return_value_ptr); if( !a->is_ref__gc && a->refcount__gc > 1 ) { zval *tmp; MAKE_STD_ZVAL(tmp); *tmp = *a; zval_copy_ctor(tmp); tmp->is_ref__gc = 0; tmp->refcount__gc = 1; zend_hash_update(&EG(symbol_table), "a", sizeof("a"), &tmp,sizeof(zval*), NULL); a = tmp; } a->is_ref__gc = 1; a->refcount__gc++; *return_value_ptr = a; } #endif /* PHP >= 5.1.0 */ ```` return_value_ptr是定义zend internal function时的另外一个重要参数,他是一个zval**类型的指针,并且指向函数的返回值。我们调用zval_ptr_dtor()函数后,默认的return_value便被废弃了。这里的$a变量如果是与某个非引用形式的变量共用一个zval的话,便要进行分离。 不幸的是,如果你编译上面的代码,使用的时候便会得到一个段错误。为了使它能够正常的工作,需要在源文件中加一些东西: ````c #if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) ZEND_BEGIN_ARG_INFO_EX(return_by_ref_arginfo, 0, 1, 0) ZEND_END_ARG_INFO () #endif /* PHP >= 5.1.0 */ 然后使用下面的代码来申明我们的定义的函数: #if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) ZEND_FE(return_by_ref, return_by_ref_arginfo) #endif /* PHP >= 5.1.0 */ ```` arginfo是一种特殊的结构体,用来提前向内核告知此函数具有的一些特定的性质,如本例,其将告诉内核本函数需要引用形式的返回值,所以内核不再通过return_value来获取执行结果,而是通过return_value_ptr。如果没有arginfo,那内核会预先把return_value_ptr置为NULL,当我们对其调用zval_ptr_dtor()函数时便会使程序崩溃。
这一些代码都包含在了一个宏里面,只有在php版本大于等于5.1的时候才会被启用。如果没有这些if、endif,那我们的程序将无法在php4下通过编译,在php5.0上也会激活一些无法预测的错误。

  • zone(zqx10104#163.com)于2011-10-20提供了一个Bug,:-)