7.6.5 函数调用
最后更新于:2022-04-02 05:18:45
### 7.6.5 函数调用
实际应用中,扩展可能需要调用用户自定义的函数或者其他扩展定义的内部函数,前面章节已经介绍过函数的执行过程,这里不再重复,本节只介绍下PHP提供的函数调用API的使用:
```c
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后返回:
```php
function mySum($i){
return $i+100;
}
```
(2)接下来在扩展中调用这个函数:
```c
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)最后调用这个内部函数:
```php
function mySum($i){
return $i+100;
}
echo my_func_1(60);
===========[output]===========
160
```
`call_user_function()`并不是只能调用PHP脚本中定义的函数,内核或其它扩展注册的函数同样可以通过此函数调用,比如:array_merge()。
```c
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));
}
```
```php
$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()`替我们完成了这个工作,下面简单看下其处理过程。
```c
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()`完成函数的调用。
```c
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);
}
}
...
}
```
';