函数调用

7.6.5 函数调用

实际应用中,扩展可能需要调用用户自定义的函数或者其他扩展定义的内部函数,前面章节已经介绍过函数的执行过程,这里不再重复,本节只介绍下PHP提供的函数调用API的使用:

ZEND_API int call_user_function(HashTable *function_table, zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[]);

各参数的含义:

  • __function_table:__ 函数符号表,普通函数是EG(function_table),如果是成员方法则是zend_class_entry.function_table
  • object: 调用成员方法时的对象
  • __function_name:__ 调用的函数名称
  • __retval_ptr:__ 函数返回值地址
  • __param_count:__ 参数数量
  • params: 参数数组

从接口的定义看其使用还是很简单的,不需要我们关心执行过程中各阶段复杂的操作。下面从一个具体的例子看下其使用:

(1)在PHP中定义了一个普通的函数,将参数$i加上100后返回:

function mySum($i){
    return $i+100;
}

(2)接下来在扩展中调用这个函数:

PHP_FUNCTION(my_func_1)
{
    zend_long   i;
    zval        call_func_name, call_func_ret, call_func_params[1];
    uint32_t    call_func_param_cnt = 1;
    zend_string *call_func_str;
    char        *func_name = "mySum";

    if(zend_parse_parameters(ZEND_NUM_ARGS(), "l", &i) == FAILURE){
        RETURN_FALSE;
    }

    //分配zend_string:调用完需要释放
    call_func_str = zend_string_init(func_name, strlen(func_name), 0);
    //设置到zval
    ZVAL_STR(&call_func_name, call_func_str);

    //设置参数
    ZVAL_LONG(&call_func_params[0], i);

    //call
    if(SUCCESS != call_user_function(EG(function_table), NULL, &call_func_name, &call_func_ret, call_func_param_cnt, call_func_params)){
        zend_string_release(call_func_str);
        RETURN_FALSE;
    }
    zend_string_release(call_func_str);
    RETURN_LONG(Z_LVAL(call_func_ret));
}

(3)最后调用这个内部函数:

function mySum($i){
    return $i+100;
}

echo my_func_1(60);
===========[output]===========
160

call_user_function()并不是只能调用PHP脚本中定义的函数,内核或其它扩展注册的函数同样可以通过此函数调用,比如:array_merge()。

PHP_FUNCTION(my_func_1)
{
    zend_array  *arr1, *arr2;
    zval        call_func_name, call_func_ret, call_func_params[2];
    uint32_t    call_func_param_cnt = 2;
    zend_string *call_func_str;
    char        *func_name = "array_merge";

    if(zend_parse_parameters(ZEND_NUM_ARGS(), "hh", &arr1, &arr2) == FAILURE){
        RETURN_FALSE;
    }
    //分配zend_string
    call_func_str = zend_string_init(func_name, strlen(func_name), 0);
    //设置到zval
    ZVAL_STR(&call_func_name, call_func_str);

    ZVAL_ARR(&call_func_params[0], arr1);
    ZVAL_ARR(&call_func_params[1], arr2);

    if(SUCCESS != call_user_function(EG(function_table), NULL, &call_func_name, &call_func_ret, call_func_param_cnt, call_func_params)){
        zend_string_release(call_func_str);
        RETURN_FALSE;
    }
    zend_string_release(call_func_str);
    RETURN_ARR(Z_ARRVAL(call_func_ret));
}

$arr1 = array(1,2);
$arr2 = array(3,4);

$arr = my_func_1($arr1, $arr2);
var_dump($arr);

你可能会注意到,上面的例子通过call_user_function()调用函数时并没有增加两个数组参数的引用计数,但根据前面介绍的内容:函数传参时不会硬拷贝value,而是增加参数value的引用计数,然后在函数return阶段再把引用减掉。实际是call_user_function()替我们完成了这个工作,下面简单看下其处理过程。

int call_user_function(HashTable *function_table, zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[])
{
    return call_user_function_ex(function_table, object, function_name, retval_ptr, param_count, params, 1, NULL);
}

int call_user_function_ex(HashTable *function_table, zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[], int no_separation, zend_array *symbol_table)
{
    zend_fcall_info fci;

    fci.size = sizeof(fci);
    fci.function_table = function_table;
    fci.object = object ? Z_OBJ_P(object) : NULL;
    ZVAL_COPY_VALUE(&fci.function_name, function_name);
    fci.retval = retval_ptr;
    fci.param_count = param_count;
    fci.params = params;
    fci.no_separation = (zend_bool) no_separation;
    fci.symbol_table = symbol_table;

    return zend_call_function(&fci, NULL);
}

call_user_function()将我们提供的参数组装为zend_fcall_info结构,然后调用zend_call_function()进行处理,还记得zend_parse_parameters()那个"f"解析符吗?它也是将输入的函数名称解析为一个zend_fcall_info,可以更方便的调用函数,同时我们也可以自己创建一个zend_fcall_info结构,然后使用zend_call_function()完成函数的调用。

int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache)
{
    ...
    for (i=0; iparam_count; i++) {
        zval *param;
        zval *arg = &fci->params[i];
        ...
        //为参数添加引用
        if (Z_OPT_REFCOUNTED_P(arg)) {
            Z_ADDREF_P(arg);
        }
    }
    ...
    //调用的是用户函数
    if (func->type == ZEND_USER_FUNCTION) {
        //执行
        zend_init_execute_data(call, &func->op_array, fci->retval);
        zend_execute_ex(call);
    }else if (func->type == ZEND_INTERNAL_FUNCTION){ //内部函数
        if (EXPECTED(zend_execute_internal == NULL)) {
            func->internal_function.handler(call, fci->retval);
        } else {
            zend_execute_internal(call, fci->retval);
        }
    }
    ...
}
联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2024

备案号:京ICP备15003423号-3