## 起步
很多时候,需要把控制权限交给用户,或者在扩展里完成某件事后去回调用户的方法。
在PHP扩展里是通过 `call_user_function_ex` 函数来调用用户空间的函数的。
## 定义
它的定义在 [Zend/zend_API.h ](https://github.com/php/php-src/blob/master/Zend/zend_API.h):
```c
#define call_user_function_ex(function_table, object, function_name, retval_ptr, param_count, params, no_separation, symbol_table)
_call_user_function_ex(object, function_name, retval_ptr, param_count, params, no_separation)
```
通过宏定义替换为_call_user_function_ex,其中参数 function_table 被移除了,它之所以在API才存在大概是为了兼容以前的写法。函数的真正定义是:
```c
ZEND_API int _call_user_function_ex(
zval *object,
zval *function_name,
zval *retval_ptr,
uint32_t param_count,
zval params[],
int no_separation);
```
参数分析:
* `zval *object`:这个是用来我们调用类里的某个方法的对象。
* `zval *function_name`:要调用的函数的名字。
* `zval *retval_ptr`:收集回调函数的返回值。
* `uint32_t param_count`:回调函数需要传递参数的个数。
* `zval params[]`: 参数列表。
* `int no_separation`:是否对zval进行分离,如果设为1则直接会出错,分离的作用是为了优化空间。
## 回调功能的实现
```c
PHP_FUNCTION(hello_callback)
{
zval *function_name;
zval retval;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &function_name) == FAILURE) {
return;
}
if (Z_TYPE_P(function_name) != IS_STRING) {
php_printf("Function require string argumnets!");
return;
}
//TSRMLS_FETCH();
if (call_user_function_ex(EG(function_table), NULL, function_name, &retval, 0, NULL, 0, NULL TSRMLS_CC) != SUCCESS) {
php_printf("Function call failed!");
return;
}
*return_value = retval;
zval_copy_ctor(return_value);
zval_ptr_dtor(&retval);
}
```
`zval_copy_ctor()`原始(zval)的内容拷贝给它。`zval_ptr_dtor()`释放空间。`return_value`不是一个函数外的变量,它的由函数声明里的变量。`PHP_FUNCTION(hello_callback)`这个声明是简写,最终会被预处理宏替换为:
```
void zif_hello_callback(zend_execute_data *execute_data, zval *return_value)
```
`return_value`变量其实也就是最终返回给调用脚本的,RETURN_STR(s) 等返回函数最终也都是宏替换为对该变量的操作。
测试脚本:
```
<?php
function fun1() {
for ($i = 0; $i < 5; $i++) {
echo 'fun1:'.$i."\n";
}
return 'call end';
}
echo hello_callback('fun1');
```
## 一个并行扩展
早期的php不支持多进程多线程的,现在随着发展有很多扩展不断完善它,诸如`pthread`,`swoole`等,不仅能多线程,而且能实现异步。
利用c语言多线程`pthread`库来实现一个简单的并行扩展。
先声明我们一会用到的结构:
```
struct myarg
{
zval *fun;
zval ret;
};
```
线程函数:
```
static void my_thread(struct myarg *arg) {
zval *fun = arg->fun;
zval ret = arg->ret;
if (call_user_function_ex(EG(function_table), NULL, fun, &ret, 0, NULL, 0, NULL TSRMLS_CC) != SUCCESS) {
return;
}
}
```
函数的实现:
```
PHP_FUNCTION(hello_thread)
{
pthread_t tid;
zval *fun1, *fun2;
zval ret1, ret2;
struct myarg arg;
int ret;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &fun1, &fun2) == FAILURE) {
return;
}
arg.fun = fun1;
arg.ret = ret1;
ret = pthread_create(&tid, NULL, (void*)my_thread, (void*)&arg);
if(ret != 0) {
php_printf("Thread Create Error\n");
exit(0);
}
if (call_user_function_ex(EG(function_table), NULL, fun2, &ret2, 0, NULL, 0, NULL TSRMLS_CC) != SUCCESS) {
return;
}
pthread_join(tid, NULL);
RETURN_NULL();
}
```
测试脚本:
```
<?php
function fun1() {
for ($i = 0; $i < 5; $i++) {
echo 'fun1:'.$i.'\n';
}
}
function fun2() {
for ($i = 0; $i < 5; $i++) {
echo 'fun2:'.$i.'\n';
}
}
hello_thread('fun1', 'fun2');
echo 'after 多并发';
```
输出:
![](https://box.kancloud.cn/d491f07e3c3eae74403708f3880c6d47_540x350.png)
两次的输出结果不一样,并且`echo 'after 多并发';`是在两个函数都运行完后才执行的。