[ PHP 内核与扩展开发系列] 函数的参数:zend_parse_paramenters

栏目: PHP · 发布时间: 6年前

内容简介:[ PHP 内核与扩展开发系列] 函数的参数:zend_parse_paramenters

基本 参数

最简单的获取函数调用者传递过来的参数便是使用 zend_parse_parameters() 函数。 zend_parse_parameters() 函数的前几个参数我们直接用内核里的宏来生成便可以了,形式为: ZEND_NUM_ARGS() TSRMLS_CC ,注意两者之间有个空格,但是没有逗号。从名字可以看出, ZEND_NUM_ARGS() 代表着参数的个数。紧接着需要传递给 zend_parse_parameters() 函数的参数是一个用于格式化的字符串,就像 printf 的第一个参数一样。下面列出了最常用的几个符号:

参数 代表着的类型
b Boolean
l Integer
d Float
s String
r Resource
a Array
o Object
O 特定类型的Object
z 任意类型
Z zval**类型
f 表示函数、方法名称

这个函数就像 printf() 函数一样,后面的参数是与格式化字符串里的格式一一对应的。一些基础类型的数据会直接映射成 C 语言里的类型。

ZEND_FUNCTION(sample_getlong) {

    long foo;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &foo) == FAILURE)
    {
        RETURN_NULL();
    }
    php_printf("The integer value of the parameter is: %ld\n", foo);
    RETURN_TRUE;
}

一般来说, intlong 这两种数据类型的数据往往是相同的,但也有例外情况。所以我们不应该把 long 的数组放在一个 int 里,尤其是在 64 位平台里,那将引发一些不容易排查的 Bug。所以通过 zend_parse_parameter() 函数接收参数时,我们应该使用内核约定好的类型变量作为载体:

参数 对应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**

注意,所有的 PHP 语言中的复合类型参数都需要 zval* 类型来作为载体,因为它们都是内核自定义的一些数据结构。我们一定要确认参数和载体的类型一致,如果需要,它可以进行类型转换,比如把 array 转换成 stdClass 对象。 sO (字母大写)类型需要特殊一些,因为它们都需要两个载体。我们将在接下来的章节里了解 PHP 中对象的具体实现。这样我们改写一下我们之前定义的一个函数:

<?php
function sample_hello_world($name) {
    echo "Hello $name!\n";
}

在编写扩展时,我们需要用 zend_parse_parameters() 来接收这个字符串:

ZEND_FUNCTION(sample_hello_world) 
{
    char *name;
    int name_len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE)
    {
            RETURN_NULL();
    }
       php_printf("Hello ");
       PHPWRITE(name, name_len);
       php_printf("!\n");
}

如果传递给函数的参数数量小于 zend_parse_parameters() 要接收的参数数量,它便会执行失败,并返回 FAILURE

如果我们需要接收多个参数,可以直接在 zend_parse_paramenters() 的参数里罗列接收载体便可以了,如:

<?php
function sample_hello_world($name, $greeting) {
    echo "Hello $greeting $name!\n";
}
sample_hello_world('John Smith', 'Mr.');

在 PHP 扩展里应该这样来实现:

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,并且 refcount__gc 等于1。

默认参数值

现在让我们继续改写 sample_hello_world() , 接下来我们使用一些参数的默认值,在 PHP 语言里就像下面这样:

<?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_parse_parameters 中的 | 参数,这个参数之前的参数被认为是必须的,之后的便认为是非必须的了,如果没有传递,则不会去修改载体。

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

如果你不传递第二个参数,则扩展函数会被认为默认而不去修改载体。所以,我们需要自己来预先设置有载体的值,它往往是是 NULL,或者一个与函数逻辑有关的值。每个 zval,包括 IS_NULL 型的 zval,都需要占用一定的内存空间,并且需要 CPU 的计算资源来为它申请内存、初始化,并在它们完成工作后释放掉。但是很多代码都都没有意识到这一点。有很多代码都会把一个 NULL 类型的值包裹成 zval 的 IS_NULL 类型,在扩展开发里这种操作是可以优化的,我们可以把参数接收成 C 语言里的 NULL。我们就这一个问题看以下代码:

ZEND_FUNCTION(sample_arg_fullnull) {
    zval *val;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &val) == FAILURE) {
        RETURN_NULL();
    }
    if (Z_TYPE_P(val) == IS_NULL) {
        val = php_sample_make_defaultval(TSRMLS_C);
    }
    ...
}

ZEND_FUNCTION(sample_arg_nullok) {
    zval *val;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!", &val) == FAILURE) {
        RETURN_NULL();
    }
    if (!val) {
        val = php_sample_make_defaultval(TSRMLS_C);
    }
}

这两段代码乍看起来并没有什么很大的不同,但是第一段代码确实需要更多的 CPU 和内存资源。可能这个技巧在平时并没多大用,不过技多不压身,知道总比不知道好。

强制分离

当一个变量被传递给函数时候,无论它是否被引用,它的 refcoung__gc 属性都会加 1,至少成为 2。一份是它自己,另一份是传递给函数的拷贝。在改变这个 zval 之前,有时会需要提前把它分成实际意义上的两份拷贝。这就是 / 格式符的作用。它将把写时复制的 zval 提前分成两个完整独立的拷贝,从而使我们可以在后面的代码中随意对其进行操作,否则我们可能需要不停的提醒自己对接收的参数进行分离等操作。和 ! 一样,该修饰符位于其所影响的类型之后。

zend_get_arguments()

如果你想让你的扩展能够兼容老版本的 PHP,或者你只想以 zval 为载体来接收参数,便可以考虑使用 zend_get_parameters() 函数来接收参数。 zend_get_parameters()zend_parse_parameters() 不同,从名字上我们便可以看出,它直接获取,而不做解析,首先,它不会自动进行类型转换,所有的参数在扩展实现中的载体都需要是 zval 类型的,下面让我们来看一个最简单的例子:

ZEND_FUNCTION(sample_onearg) {
    zval *firstarg;

    if (zend_get_parameters(ZEND_NUM_ARGS(), 1, &firstarg)== FAILURE) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Expected at least 1 parameter.");
        RETURN_NULL();
    }
    /* Do something with firstarg... */
}

其次, zend_get_parameters() 在接收失败的时候,并不会自己抛出错误,它也不能方便的处理具有默认值的参数。最后一点与 zend_parse_parameters 不同的是,它会自动的把所有符合 copy-on-write 的 zval 进行强制分离,生成一个崭新的拷贝送到函数内部。如果你希望用它其它的特性,而唯独不需要这个功能,可以去尝试一下用 zend_get_parameters_ex() 函数来接收参数。为了不对 copy-on-write 的变量进行分离操作, zend_get_parameters_ex() 的参数是 zval** 类型的,而不是 zval* 。这个函数不太经常用,可能只会在你碰到一些极端问题时候才会想到它,而它用起来却很简单:

ZEND_FUNCTION(sample_onearg) {
    zval **firstarg;
    if (zend_get_parameters_ex(1, &firstarg) == FAILURE) {
        WRONG_PARAM_COUNT;
    }
    /* Do something with firstarg... */
}

注意: zend_get_parameters_ex 不需要 ZEND_NUM_ARGS() 作为参数,因为它是在是在后期加入的,那个参数已经不再需要了。

上面例子中还使用了 WRONG_PARAM_COUNT 宏,它的功能是抛出一个 E_WARNING 级别的错误信息,并自动 return。

可变参数

有两种其它的 zend_get_parameter_** 函数,专门用来解决参数很多或者无法提前知道参数数目的问题。想一下 PHP 语言中 var_dump() 函数的用法,我们可以向其传递任意数量的参数,它在内核中的实现其实是这样的:

ZEND_FUNCTION(var_dump) {
    int i, argc = ZEND_NUM_ARGS();
    zval ***args;

    args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0);
    if (ZEND_NUM_ARGS() == 0 || zend_get_parameters_array_ex(argc, args) == FAILURE) {
        efree(args);
        WRONG_PARAM_COUNT;
    }

    for (i=0; i<argc; i++) {
            php_var_dump(args[i], 1 TSRMLS_CC);
    }

    efree(args);
}

程序首先获取参数数量,然后通过 safe_emalloc 函数申请了相应大小的内存来存放这些 zval** 类型的参数。这里使用了 zend_get_parameters_array_ex() 函数来把传递给函数的参数填充到 args 中。你可能已经立即想到,还存在一个名为 zend_get_parameters_array() 的函数,唯一不同的是它将 zval* 类型的参数填充到 args 中,并且需要 ZEND_NUM_ARGS() 作为参数。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Web Application Hacker's Handbook

The Web Application Hacker's Handbook

Dafydd Stuttard、Marcus Pinto / Wiley / 2011-9-27 / USD 50.00

The highly successful security book returns with a new edition, completely updated Web applications are the front door to most organizations, exposing them to attacks that may disclose personal infor......一起来看看 《The Web Application Hacker's Handbook》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器