## 起步
很多時候,需要把控制權限交給用戶,或者在擴展里完成某件事后去回調用戶的方法。
在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 多并發';
```
輸出:

兩次的輸出結果不一樣,并且`echo 'after 多并發';`是在兩個函數都運行完后才執行的。