最简单的获取函数调用者传递过来的参数便是使用 zend_parse_parameters()
函数,形式为:ZEND_NUM_ARGS() TSRMLS_CC
注意两者之间有个空格,但是没有逗号。从名字可以看出,ZEND_NUM_ARGS()
代表着参数的个数。紧接着需要传递给 zend_parse_parameters()
函数的参数是一个用于格式化的字符串。
函数的参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 type_spec是格式化字符串,其常见的含义如下: 参数 代表着的类型 b Boolean l Integer 整型 d Floating point 浮点型 s String 字符串 r Resource 资源 a Array 数组 o Object instance 对象 O Object instance of a specified type 特定类型的对象 z Non-specific zval 任意类型~ Z zval**类型 参数 对应C里的数据类型 b zend_bool l long d double s char*, int 前者接收指针,后者接收长度 r zval* a zval* o zval* O zval*, zend_class_entry* z zval* Z zval**
后面的参数是与格式化字符串里的格式一一对应的。一些基础类型的数据会直接映射成 C 语言里的类型。如果是字符串,也就是s,那么后面接受参数还要有个字符串长度,下面看个例子:
1 2 3 4 5 <?php function sample_hello_world ($name, $greeting) { echo "Hello $greeting $name!\n" ; } sample_hello_world('John Smith' , 'Mr.' );
如果传递给函数的参数数量小于 zend_parse_parameters()
要接收的参数数量,它便会执行失败,并返回 FAILURE
。 如果我们需要接收多个参数,可以直接在 zend_parse_paramenters()
的参数里罗列接收载体便可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ZEND_FUNCTION(sample_hello_world) { char *name; int name_len; char *greeting; int greeting_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss" ,&name, &name_len, &greeting, &greeting_len) == FAILURE) { RETURN_NULL(); } php_printf("Hello " ); PHPWRITE(greeting, greeting_len); php_printf(" " ); PHPWRITE(name, name_len); php_printf("!\n" ); }
除此之外,还有其他三个格式化参数用来增强我们接收参数的能力:
1 2 3 | 它之前的参数都是必须的,之后的都是非必须的,也就是有默认值的。 ! 如果接收了一个PHP语言里的null变量,则直接把其转成C语言里的NULL,而不是封装成IS_NULL类型的zval。 / 如果传递过来的变量与别的变量共用一个zval,而且不是引用,则进行强制分离,新的zval的is_ref__gc==0, and refcount__gc==1.
函数参数的默认值 1 2 3 4 5 6 <?php function sample_hello_world ($name, $greeting='Mr./Ms.' ) { echo "Hello $greeting $name!\n" ; } sample_hello_world('Ginger Rogers' ,'Ms.' ); sample_hello_world('Fred Astaire' );
此时即可以只向 sample_hello_world 中传递一个参数,也可以传递完整的两个参数。 那同样的功能我们怎样在扩展函数里实现呢?我们需要借助 | 这个格式化参数,这个参数之前的参数被认为是必须的,之后的便认为是非必须的了,如果没有传递,则不会去修改载体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ZEND_FUNCTION(sample_hello_world) { char *name; int name_len; char *greeting = "Mr./Mrs."; int greeting_len = sizeof("Mr./Mrs.") - 1; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &name, &name_len, &greeting, &greeting_len) == FAILURE) { RETURN_NULL(); } php_printf("Hello "); PHPWRITE(greeting, greeting_len); php_printf(" "); PHPWRITE(name, name_len); php_printf("!\n"); }
函数的返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php function sample_long () { return 42 ; } $bar = sample_long(); int sample_long(void) { return 42 ; } int main(void) { int bar = sample_long(); return 1 ; }
那么问题来了,扩展中编写的PHP函数如何把返回值回馈给用户端的函数调用者呢? 我们想当然的写出了下面一段代码:
1 2 3 4 5 6 7 8 9 ZEND_FUNCTION(sample_long_wrong) { zval *retval; MAKE_STD_ZVAL(retval); ZVAL_LONG(retval, 42 ); return retval; }
上面的写法是无效的!与其让扩展开发员每次都初始化一个 zval 并 return 之,zend 引擎早就准备好了一个更好的方法。它在每个 zif 函数声明里加了一个 zval* 类型的形参,名为 return_value,专门来解决返回值这个问题。
在之前我们已经知道 ZEND_FUNCTION 展开后有个 INTERNAL_FUNCTION_PARAMETERS 参数,我们看下这个展开后是什么:
1 define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC
参数说明:
1 2 3 4 5 int ht zval *return_value,我们在函数内部修改这个指针,函数执行完成后,内核将把这个指针指向的zval返回给用户端的函数调用者。 zval **return_value_ptr, zval *this_ptr,如果此函数是一个类的方法,那么这个指针的含义和PHP语言中$this变量差不多。 int return_value_used,代表用户端在调用此函数时有没有使用到它的返回值。
现在我们看下刚才的例子,正确的写法为:
1 2 3 4 5 ZEND_FUNCTION(sample_long) { ZVAL_LONG(return_value, 42 ); return ; }
ZEND_FUNCTION 本身并没有通过 return 关键字返回任何有价值的东西,它只不过是在运行时修改了 return_value 指针所指向的变量的值而已,而内核则会把 return_value 指向的变量作为用户端调用此函数后的得到的返回值。
return_value 如此重要,内核肯定早已经为它准备了大量的宏,来简化我们的操作,接下来我们要介绍的宏的名字是:RETVAL。 再回到上面的那个例子,我们用 RETVAL 来重写一下:
1 2 3 4 5 6 ZEND_FUNCTION(sample_long) { RETVAL_LONG(42); //展开后相当与ZVAL_LONG(return_value, 42); return; }
甚至,内核中还提供了 RETURN_ 系列宏来为我们自动补上 return,我们再改写一下;
1 2 3 4 ZEND_FUNCTION(sample_long) { RETURN_LONG(42); }