### 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超時控制的思考