一个函数的执行结果要返回给调用者,除了使用return功能,还有一种办法,那就是以引用的形式传递参数,然后在内部修改这个参数的值。前一种方法往往只能返回一个值,如果我们的函数执行结果具有多种数据,便需要把这些数据打包到一个数组、类等复合类型的变量中才能得以实现;但后一种方法相比而言就简单一些了。
运行时传递引用:Call-time Pass-by-ref
标题有点绕口,其实很简单,功能如以下php代码所示:
02 | function byref_calltime( $a ) { |
03 | $a = '(modified by ref!)' ; |
06 | $foo = 'I am a string' ; |
我们在传递参数的时候使用&操作符,便可以传递$foo变量的引用过去,而不是copy一份。当我们在函数内核修改这个参数时,函数外部的$foo也跟着被一起修改了。同样的功能我们如何在扩展里实现呢,其实很简单,请看下面的源码:
01 | ZEND_FUNCTION(byref_calltime) |
06 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z" , &a) == FAILURE) |
21 | ZVAL_STRING(a, " (modified by ref!)" ,1); |
编译时的传递引用Compile-time Pass-by-ref
如果每一次都在调用函数时候都对参数加一个&符号真是太罗嗦了,有没有一个简单的办法呢,比如在定义函数的时候便声明这个参数是引用形式的,而不用用户自己加&符号表示引用,而由内核来完成这步操作?这个功能是有的,我们在PHP语言中可以这样实现。
03 | function byref_compiletime(& $a ) { |
04 | $a = ' (modified by ref!)' ; |
06 | $foo = 'I am a string' ; |
09 | byref_compiletime( $foo ); |
上面的代码中,我们只是把引用符号从函数调用里转移到函数定义里。此功能在扩展里面实现的话就颇费周折了,我们需要提前为它定义一个arginfo结构体来向内核通知此函数的这个特定行为。添加此函数到module_entry里需要这样:
1 | ZEND_FE(byref_compiletime, byref_compiletime_arginfo) |
byref_compiletime_arginfo十一个arginfo结构体,我们在前面的章节中已经用过一次了。
在Zend Engine 2 (PHP5+)中,arginfo的数据是由多个zend_arg_info结构体构成的数组,数组的每一个成员即每一个zend_arg_info结构体处理函数的一个参数。zend_arg_info结构体的定义如下:
01 | typedef struct _zend_arg_info { |
04 | const char *class_name; |
05 | zend_uint class_name_len; |
06 | zend_bool array_type_hint; |
08 | zend_bool pass_by_reference; |
09 | zend_bool return_reference; |
10 | int required_num_args; |
生成zend_arg_info结构的数组比较繁琐,为了方便PHP扩展开发者,内核已经准备好了相应的宏来专门处理此问题,首先先用一个宏函数来生成头部,然后用第二个宏生成具体的数据,最后用一个宏生成尾部代码。
01 | #define ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference) ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, ZEND_RETURN_VALUE, -1) |
02 | #define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \ |
03 | static const zend_arg_info name[] = { \ |
04 | { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args }, |
07 | #define ZEND_ARG_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, 0, pass_by_ref, 0, 0 }, |
08 | #define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, NULL, 0, 0, 0, pass_by_ref, 0, 0 }, |
09 | #define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, 0, allow_null, pass_by_ref, 0, 0 }, |
10 | #define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, sizeof(#name)-1, NULL, 0, 1, allow_null, pass_by_ref, 0, 0 }, |
13 | #define ZEND_END_ARG_INFO() }; |
16 | ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference) |
17 | ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference,required_num_args) |
这两个宏函数的前两个参数的含义是一样的,name便是这个zend_arg_info数组变量的名字,这里我们定义它为:byref_compiletime_arginfo。pass_rest_by_reference如果被赋值为1,则代表着所有的参数默认都是需要以引用的方式传递的(在arginfo中单独声明的除外)。而对于ZEND_BEGIN_ARG_INFO_EX的后两个参数:
- name和pass_rest_by_reference的含义同上。
- return_reference:声明这个函数的返回值需要以引用的形式返回,这个参数已经在前面章节用过了。
- required_num_args:函数被调用时,传递参数至少为前N个函数(也就是后面参数都有默认值),当设置为-1时,必须传递所有参数
接下来让我们看生成具体数据的宏:
01 | ZEND_ARG_PASS_INFO(by_ref) |
05 | ZEND_ARG_INFO(by_ref, name) |
09 | ZEND_ARG_ARRAY_INFO(by_ref, name, allow_null) |
10 | ZEND_ARG_OBJ_INFO(by_ref, name, classname, allow_null) |
11 | 这两个宏实现了类型绑定,也就是说我们在传递某个参数时,必须是数组类型或者某个类的实例。如果最后的参数为真,则除了绑定的数据类型,还可以传递一个NULL数据。 |
14 | ZEND_BEGIN_ARG_INFO(byref_compiletime_arginfo, 0) |
为了使我们的扩展能够兼容PHP4,还需要使用#ifdef进行特殊处理。
2 | ZEND_BEGIN_ARG_INFO(byref_compiletime_arginfo, 0) |
6 | static unsigned char byref_compiletime_arginfo[] = { 1, BYREF_FORCE }; |
我们copy一份ZEND_FUNCTION(byref_calltime)的实现,并重名成ZEND_FUNCTION(byref_compiletime)就行了。或者直接弄个ZEND_FALIAS就行了:
1 | ZEND_FE(byref_compiletime,byref_calltime,byref_compiletime_arginfo) |
没有帐号? 现在注册.