PHP 扩展开发学习笔记(2)- 函数参数与返回值

函数的参数

最简单的获取函数调用者传递过来的参数便是使用zend_parse_parameters()函数,形式为:ZEND_NUM_ARGS() TSRMLS_CC,注意两者之间有个空格,但是没有逗号。从名字可以看出,ZEND_NUM_ARGS()代表着参数的个数。紧接着需要传递给zend_parse_parameters()函数的参数是一个用于格式化的字符串。

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,那么后面接受参数还要有个字符串长度,下面看个例子:

<?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()的参数里罗列接收载体便可以了

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");
}

除此之外,还有其他三个格式化参数用来增强我们接收参数的能力:

|    它之前的参数都是必须的,之后的都是非必须的,也就是有默认值的。
!    如果接收了一个PHP语言里的null变量,则直接把其转成C语言里的NULL,而不是封装成IS_NULL类型的zval。
/    如果传递过来的变量与别的变量共用一个zval,而且不是引用,则进行强制分离,新的zval的is_ref__gc==0, and refcount__gc==1.

函数参数的默认值

<?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中传递一个参数,也可以传递完整的两个参数。 那同样的功能我们怎样在扩展函数里实现呢?我们需要借助 | 这个格式化参数,这个参数之前的参数被认为是必须的,之后的便认为是非必须的了,如果没有传递,则不会去修改载体。

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");
}

函数的返回值

PHP语言中函数的返回值是通过return来完成的,C语言也一样使用return关键字,例如:

// 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函数如何把返回值回馈给用户端的函数调用者呢? 我们想当然的写出了下面一段代码:

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参数,我们看下这个展开后是什么:

define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

参数说明:

int ht
zval *return_value,我们在函数内部修改这个指针,函数执行完成后,内核将把这个指针指向的zval返回给用户端的函数调用者。
zval **return_value_ptr,
zval *this_ptr,如果此函数是一个类的方法,那么这个指针的含义和PHP语言中$this变量差不多。
int return_value_used,代表用户端在调用此函数时有没有使用到它的返回值。

现在我们看下刚才的例子,正确的写法为:

ZEND_FUNCTION(sample_long)
{
    ZVAL_LONG(return_value, 42);
    return;
}

ZEND_FUNCTION本身并没有通过return关键字返回任何有价值的东西,它只不过是在运行时修改了return_value指针所指向的变量的值而已,而内核则会把return_value指向的变量作为用户端调用此函数后的得到的返回值。

return_value如此重要,内核肯定早已经为它准备了大量的宏,来简化我们的操作,接下来我们要介绍的宏的名字是:RETVAL。 再回到上面的那个例子,我们用RETVAL来重写一下:

ZEND_FUNCTION(sample_long)
{
    RETVAL_LONG(42);
    //展开后相当与ZVAL_LONG(return_value, 42);
    return;
}

甚至,内核中还提供了RETURN_系列宏来为我们自动补上return,我们再改写一下;

ZEND_FUNCTION(sample_long)
{
    RETURN_LONG(42);
}

引用与函数执行的结果