0%

PHP5 扩展开发(2)- 函数参数与返回值

最简单的获取函数调用者传递过来的参数便是使用 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语言
<?php
function sample_long() {
return 42;
}
$bar = sample_long();

// C语言
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);
}