企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 6.1 函数返回值 # 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;如: ````c 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 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,:-) ## links - 6 [函数返回值](6.html) - 6.2 [引用与函数的执行结果](6.2.html)