### 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; i<fci->param_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);
}
}
...
}
```
- 前言
- 第1章 PHP基本架构
- 1.1 PHP简介
- 1.2 PHP7的改进
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本实现
- 1.3.3 FPM的初始化
- 1.3.4 请求处理
- 1.3.5 进程管理
- 1.4 PHP执行的几个阶段
- 第2章 变量
- 2.1 变量的内部实现
- 2.2 数组
- 2.3 静态变量
- 2.4 全局变量
- 2.5 常量
- 第3章 Zend虚拟机
- 3.1 PHP代码的编译
- 3.1.1 词法解析、语法解析
- 3.1.2 抽象语法树编译流程
- 3.2 函数实现
- 3.2.1 内部函数
- 3.2.2 用户函数的实现
- 3.3 Zend引擎执行流程
- 3.3.1 基本结构
- 3.3.2 执行流程
- 3.3.3 函数的执行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向对象实现
- 3.4.1 类
- 3.4.2 对象
- 3.4.3 继承
- 3.4.4 动态属性
- 3.4.5 魔术方法
- 3.4.6 类的自动加载
- 3.5 运行时缓存
- 3.6 Opcache
- 3.6.1 opcode缓存
- 3.6.2 opcode优化
- 3.6.3 JIT
- 第4章 PHP基础语法实现
- 4.1 类型转换
- 4.2 选择结构
- 4.3 循环结构
- 4.4 中断及跳转
- 4.5 include/require
- 4.6 异常处理
- 第5章 内存管理
- 5.1 Zend内存池
- 5.2 垃圾回收
- 第6章 线程安全
- 6.1 什么是线程安全
- 6.2 线程安全资源管理器
- 第7章 扩展开发
- 7.1 概述
- 7.2 扩展的实现原理
- 7.3 扩展的构成及编译
- 7.3.1 扩展的构成
- 7.3.2 编译工具
- 7.3.3 编写扩展的基本步骤
- 7.3.4 config.m4
- 7.4 钩子函数
- 7.5 运行时配置
- 7.5.1 全局变量
- 7.5.2 ini配置
- 7.6 函数
- 7.6.1 内部函数注册
- 7.6.2 函数参数解析
- 7.6.3 引用传参
- 7.6.4 函数返回值
- 7.6.5 函数调用
- 7.7 zval的操作
- 7.7.1 新生成各类型zval
- 7.7.2 获取zval的值及类型
- 7.7.3 类型转换
- 7.7.4 引用计数
- 7.7.5 字符串操作
- 7.7.6 数组操作
- 7.8 常量
- 7.9 面向对象
- 7.9.1 内部类注册
- 7.9.2 定义成员属性
- 7.9.3 定义成员方法
- 7.9.4 定义常量
- 7.9.5 类的实例化
- 7.10 资源类型
- 7.11 经典扩展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空间
- 8.1 概述
- 8.2 命名空间的定义
- 8.2.1 定义语法
- 8.2.2 内部实现
- 8.3 命名空间的使用
- 8.3.1 基本用法
- 8.3.2 use导入
- 8.3.3 动态用法
- 附录
- break/continue按标签中断语法实现
- defer推迟函数调用语法的实现
- 一起线上事故引发的对PHP超时控制的思考