💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 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); } } ... } ```