[ PHP 内核与扩展开发系列] 函数的参数:arginfo 与类型绑定

在前面的章节中我们已经介绍过 arginfo 了,下面我们看一下如何通过其实现类型绑定,但这个特性只能在 Zend Engine 2 也就是 PHP 5 中使用。

让我们再回顾一下 arginfo 的结构,每一个 arginfo 结构的声明都是通过 ZEND_BEGIN_ARG_INFO() 或者 ZEND_BEGIN_ARG_INFO_EX()函数开始的,然后紧跟着几行 ZEND_ARG_*_INFO() 宏函数,最终以 ZEND_END_ARG_INFO() 宏函数结束。每个宏的基本作用我们可以在引用参数与函数的执行结果一节已经看到。如果我们想重写一下 PHP 语言中的 count() 函数,可以这么做:

ZEND_FUNCTION(sample_count_array)
{
    zval *arr;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE)
    {
        RETURN_NULL();
    }
    RETURN_LONG(zend_hash_num_elements(Z_ARRVAL_P(arr)));
}

zend_parse_parameters() 本身可以保证传递过来的参数是一个数组,但是如果我们通过zend_get_parameters() 函数来接收参数的话就没这么幸运了,需要我们自己进行类型校对。如果想让内核自动完成类型校对,便需要 arginfo 上场了:

ZEND_BEGIN_ARG_INFO(php_sample_array_arginfo, 0)
    ZEND_ARG_ARRAY_INFO(0, arr, 0)
ZEND_END_ARG_INFO()

...

PHP_FE(sample_count_array, php_sample_array_arginfo)

...

这样我们便把类型校对的工作交给了 Zend Engine,是不是有种如释重负的感觉!同时你还给参数起了个名字,这样,对于使用你的 API 的开发人员而言,生成的错误信息将更加有意义。我们同样可以对参数中的对象进行校验,限制其是继承自某个类或者实现了某个接口等等:

ZEND_BEGIN_ARG_INFO(php_sample_class_arginfo, 0)
    ZEND_ARG_OBJ_INFO(1, obj, stdClass, 0)
ZEND_END_ARG_INFO()

需要注意的是,此时第一个参数的值是数字 1,代表着以引用的方式传递。其实这个参数对于对象来说几乎没用,因为 Zend Engine 2 中所有的对象在当作函数参数的时候都是默认以引用的形式传递的。但是我们又必须把这个参数设置为数字 1,除非你不想让你的扩展与 PHP 4 兼容。在 PHP 4 中,对象传递的是一个完整拷贝,而非通过引用。

对于数组和对象参数,不要忘记最后的允许为 NULL 的参数。更多的信息请参考引用参数与函数的执行结果一节的有关叙述。
通过 arginfo 的方式来实现类型绑定的功能只对 Zend Engine 2 有效,也就是 PHP 5+。如果你想在PHP 4 上实现相应的功能,那需要用 zend_get_parameters() 函数来接收参数,然后通过Z_TYPE_P() 宏函数来检测参数的类型或者通过 convert_to_type() 函数进行类型转换。

现在我们已经可以编写一个更真实的函数了,既可以接收用户传递过来的参数,也可以返回数据给调用者。为了写出高质量的代码,还需要我们多花点心思在 zval 的写时复制等特殊机制上,否则便会在接收参数和返回数据时留下一些 bug。后续的章节里,让我们去看一下 PHP 语言里强大的数组类型是如何在内核中实现的,去探究内核中的 HashTable 结构,从而能编写出更强大的 PHP 扩展。

学院君 has written 703 articles

资深PHP工程师,Laravel学院院长

发表评论

标记为*的字段是必填项(邮箱地址不会被公开)

你可以使用这些HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>