菜鸟学php扩展 之 详解php扩展的参数返回(五) - Go语言中文社区

菜鸟学php扩展 之 详解php扩展的参数返回(五)


转载请附上本文地址:http://blog.csdn.net/u011957758/article/details/72719666

前言

经过前文 菜鸟学php扩展 之 详解php扩展的变量(四) ,基本上已经明白了php调用扩展时候,变量的一系列基础操作了,此文主要记录扩展将一系列逻辑处理操作变量完后,是如何返回结果的。

正文

重新认识PHP_FUNCTION

打开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?,关键在于认识到返回参数其实我们是在操作指针return_value.

在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; }

return_value_used?

当调用一个函数,却没有使用到返回时的时候,会发现内存占了半天,竟然白白浪费了。如以下例子:

<?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();
    }
}

return_value_ptr?

正常情况下是返回数值,但是有一种情况是地址。

$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_*

如果你觉得有收获~可以关注我的公众号【咖啡色的羊驼】~第一时间收到我的分享和知识梳理~
在这里插入图片描述

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u011957758/article/details/72719666
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢