社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
转载请附上本文地址:http://blog.csdn.net/u011957758/article/details/72719666
经过前文 菜鸟学php扩展 之 详解php扩展的变量(四) ,基本上已经明白了php调用扩展时候,变量的一系列基础操作了,此文主要记录扩展将一系列逻辑处理操作变量完后,是如何返回结果的。
打开main/php.h会发现PHP_FUNCTION原来就是ZEND_FUNCITON
#define PHP_FUNCTION ZEND_FUNCTION
继续前行,ZEND_FUNCTION是怎么定义的?
在Zend/zend_API.h发现定义:
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
原来展开后是void name(INTERNAL_FUNCTION_PARAMETERS)的形式,现在是我们展开代表参数声明的INTERNAL_FUNCTION_PARAMETERS宏的时候了。
在Zend/zend.h中找到:
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC
关键参数解释一波:
参数 | 解释 |
---|---|
zval *return_value | 在函数内部修改这个指针,函数执行完成后,内核将把这个指针指向的zval返回给用户端的函数调用者。 |
zval **return_value_ptr | 用引用的形式返回结果时候会用到,同时废弃 return_value(下文有讲解) |
zval *this_ptr | 如果此函数是一个类的方法,那么这个指针的含义和PHP语言中$this变量差不多 |
int return_value_used | 代表用户端在调用此函数时有没有使用到它的返回值。 |
在php中是这样返回
function test_function() {
return 123;
}
然而在写扩展时候,并不是这样的:
PHP_FUNCTION(sample_long) {
zval *return_val;
MAKE_STD_ZVAL(return_val);//申请一块内存
ZVAL_LONG(return_val, 42);//赋值
return return_val;
}
而是,仅仅只改了return_value的值就return了:
ZEND_FUNCTION(sample_long) {
ZVAL_LONG(return_value, 123);
return;
}
是的,就是这样,和php对比有点想在return后面加点啥的感觉,却又不能加。
既然返回值固定是改return_value,那么每回都写不是很麻烦?
是的,在Zend/zend_API.h发现,php作者已经早想好了新的宏:
#define RETVAL_LONG(l) ZVAL_LONG(return_value, l)
改写上文例子:
PHP_FUNCTION(sample_long){
RETVAL_LONG(123); //展开后相当与ZVAL_LONG(return_value, 123);
return;
}
是不是发现,每回都要写return;?而且对于php玩家,又不能在return后加东西,真的很难过。
是的,在Zend/zend_API.h发现,php作者又已经早想好了新的宏:
#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
改写上文例子:
PHP_FUNCTION(sample_long){
RETURN_LONG(123);
}
大功告成。当前其他的类型也有对应的宏,演化过程类型不一一讲解。
贴上源码(在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
function get_arr() {
$ret = [];
for($i = 0; $i < 1000; $i++) {
$ret[] = $i;
}
return $ret;
}
get_arr();
在php确实没法做到事前预知是否有没有用到返回值,但是在写php扩展时候是可以做到的,这时候就需要使用到return_value_used的属性来提前预知是否有使用到返回值,防止内存浪费的情况。
PHP_FUNCTION(get_arr){
if (return_value_used) {
int i;
//把返回值初始化成一个PHP语言中的数组
array_init(return_value);
for(i = 0; i < 1000; i++) {
//向retrun_value里不断的添加新元素,值为i
add_next_index_long(return_value, i);
}
return;
} else {
//抛出一个E_NOTICE级错误
php_error_docref(NULL TSRMLS_CC, E_NOTICE,"我就知道你没使用到返回的值");
RETURN_NULL();
}
}
正常情况下是返回数值,但是有一种情况是地址。
$a = 'china';
function &return_by_ref(){
global $a;
return $a;
}
$b = &return_by_ref();
$b = "php";
echo $a;//此时程序输出php
那么在php扩展的时候,是怎么体现的?
这时候就需要使用到return_value_ptr来代替return_value进行返回。
前方高能,反正我是看懂了。
PHP_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;
}
以上代码需要php5.1才可以执行,5.1之前没实现这功能,但我们生活在一个幸福的年代,目前市面上基本上都是5.3版本以上甚至7.0版本的php。不需要考虑这个问题。
执行上文代码,依旧会报段错误。源文件加上下面代码就不会了:
ZEND_BEGIN_ARG_INFO_EX(return_by_ref_arginfo, 0, 1, 0)
ZEND_END_ARG_INFO ()
然后使用下面的代码来申明我们的定义的函数:
ZEND_FE(return_by_ref, return_by_ref_arginfo)
搞定返回。记住了RETURN_*
如果你觉得有收获~可以关注我的公众号【咖啡色的羊驼】~第一时间收到我的分享和知识梳理~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!