### 7.6.3 引用传参 上一节介绍了如何在内部函数中解析参数,这里还有一种情况没有讲到,那就是引用传参: ```php $a = array(); function my_func(&$a){ $a[] = 1; } ``` 上面这个例子在函数中对$a的修改将反映到原变量上,那么这种用法如何在内部函数中实现呢?上一节介绍参数解析的过程中并没有提到用户函数中参数的zend_arg_info结构,内部函数中也有类似的一个结构用于函数注册时指定参数的一些信息:zend_internal_arg_info。 ```c typedef struct _zend_internal_arg_info { const char *name; //参数名 const char *class_name; zend_uchar type_hint; //显式声明的类型 zend_uchar pass_by_reference; //是否引用传参 zend_bool allow_null; //是否允许参数为NULL,类似"!"的用法 zend_bool is_variadic; //是否为可变参数 } zend_internal_arg_info; ``` 这个结构几乎与zend_arg_info完全一样,不同的地方只在于name、class_name的类型,zend_arg_info这两个成员的类型都是zend_string。如果函数需要使用引用类型的参数或返回引用就需要创建函数的参数数组,这个数组通过:`ZEND_BEGIN_ARG_INFO()或ZEND_BEGIN_ARG_INFO_EX()`、`ZEND_END_ARG_INFO()`宏定义: ```c #define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) #define ZEND_BEGIN_ARG_INFO(name, _unused) ``` * __name:__ 参数数组名,注册函数`PHP_FE(function, arg_info)`会用到 * ___unused:__ 保留值,暂时无用 * __return_reference:__ 返回值是否为引用,一般很少会用到 * __required_num_args:__ required参数数 这两个宏需要与`ZEND_END_ARG_INFO()`配合使用: ```c ZEND_BEGIN_ARG_INFO_EX(arginfo_my_func_1, 0, 0, 2) ... ZEND_END_ARG_INFO() ``` 接着就是在上面两个宏中间定义每一个参数的zend_internal_arg_info,PHP提供的宏有: ```c //pass_by_ref表示是否引用传参,name为参数名称 #define ZEND_ARG_INFO(pass_by_ref, name) { #name, NULL, 0, pass_by_ref, 0, 0 }, //只声明此参数为引用传参 #define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, NULL, 0, pass_by_ref, 0, 0 }, //显式声明此参数的类型为指定类的对象,等价于PHP中这样声明:MyClass $obj #define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, #classname, IS_OBJECT, pass_by_ref, allow_null, 0 }, //显式声明此参数类型为数组,等价于:array $arr #define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, NULL, IS_ARRAY, pass_by_ref, allow_null, 0 }, //显式声明为callable,将检查函数、成员方法是否可调 #define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, NULL, IS_CALLABLE, pass_by_ref, allow_null, 0 }, //通用宏,自定义各个字段 #define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, NULL, type_hint, pass_by_ref, allow_null, 0 }, //声明为可变参数 #define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, NULL, 0, pass_by_ref, 0, 1 }, ``` 举个例子来看: ```php function my_func_1(&$a, Exception $c){ ... } ``` 用内核实现则可以这么定义: ```c ZEND_BEGIN_ARG_INFO_EX(arginfo_my_func_1, 0, 0, 1) ZEND_ARG_INFO(1, a) //引用 ZEND_ARG_OBJ_INFO(0, b, Exception, 0) //注意:这里不要把字符串加"" ZEND_END_ARG_INFO() ``` 展开后: ```c static const zend_internal_arg_info name[] = { //多出来的这个是给返回值用的 { (const char*)(zend_uintptr_t)(2), NULL, 0, 0, 0, 0 }, { "a", NULL, 0, 0, 0, 0 }, { "b", "Exception", 8, 1, 0, 0 }, } ``` 第一个数组元素用于记录必传参数的数量以及返回值是否为引用。定义完这个数组接下来就需要把这个数组告诉函数: ```c const zend_function_entry mytest_functions[] = { PHP_FE(my_func_1, arginfo_my_func_1) PHP_FE(my_func_2, NULL) PHP_FE_END //末尾必须加这个 }; ``` 引用参数通过`zend_parse_parameters()`解析时只能使用"z"解析,不能再直接解析为zend_value了,否则引用将失效: ```c PHP_FUNCTION(my_func_1) { zval *lval; //必须为zval,定义为zend_long也能解析出,但不是引用 zval *obj; if(zend_parse_parameters(ZEND_NUM_ARGS(), "zo", &lval, &obj) == FAILURE){ RETURN_FALSE; } //lval的类型为IS_REFERENCE zval *real_val = Z_REFVAL_P(lval); //获取实际引用的zval地址:&(lval.value->ref.val) Z_LVAL_P(real_val) = 100; //设置实际引用的类型 } ``` ```php $a = 90; $b = new Exception; my_func_1($a, $b); echo $a; ==========[output]=========== 100 ``` > __Note:__ 参数数组与zend_parse_parameters()有很多功能重合,两者都会生效,对zend_internal_arg_info验证在zend_parse_parameters()之前,为避免混乱两者应该保持一致;另外,虽然内部函数的参数数组并不强制定义声明,但还是建议声明。