`

PHP内核探索:zend_parse_parameters函数

    博客分类:
  • PHP
阅读更多
最简单的获取函数调用者传递过来的参数便是使用zend_parse_parameters()函数。

zend_parse_parameters()函数的前几个参数我们直接用内核里宏来生成便可以了,形式为:ZEND_NUM_ARGS() TSRMLS_CC,注意两者之间有个空格,但是没有逗号。从名字可以看出,ZEND_NUM_ARGS()代表这参数的个数。

紧接着需要传递个zend_parse_parameters()函数的参数是一个用于格式化的字符串,就像printf的第一个参数一样。下面表示了最常用的几个符号。

01
type_spec是格式化字符串,其常见的含义如下:
02
参数   代表着的类型
03
b   Boolean
04
l   Integer 整型
05
d   Floating point 浮点型
06
s   String 字符串
07
r   Resource 资源
08
a   Array 数组
09
o   Object instance 对象
10
O   Object instance of a specified type 特定类型的对象
11
z   Non-specific zval 任意类型~
12
Z   zval**类型
13
f   表示函数、方法名称,PHP5.1里貌似木有... ...
这个函数就像printf()函数一样,后面的参数是与格式化字符串里的格式一一对应的。一些基础类型的数据会直接映射成C语言里的类型。

01
ZEND_FUNCTION(sample_getlong)
02
{
03
    long foo;
04
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"l", &foo) == FAILURE)
05
    {
06
        RETURN_NULL();
07
    }
08
    php_printf("The integer value of the parameter is: %ld\n", foo);
09
    RETURN_TRUE;
10
}
一般来说,int和long这两种数据类型的数据往往是相同的,但也有例外情况。所以我们不应改把long的数组放在一个int里,尤其是在64位平台里,那将引发一些不容易排查的Bug。所以通过zend_parse_parameter()函数接收参数时,我们应该使用内核约定好的那些类型的变量作为载体。

01
参数  对应C里的数据类型
02
b   zend_bool
03
l   long
04
d   double
05
s   char*, int 前者接收指针,后者接收长度
06
r   zval*
07
a   zval*
08
o   zval*
09
O   zval*, zend_class_entry*
10
z   zval*
11
Z   zval**
注意,所有的PHP语言中的复合类型参数都需要zval*类型来作为载体,因为它们都是内核自定义的一些数据结构。我们一定要确认参数和载体的类型一直,如果需要,它可以进行类型转换,比如把array转换成stdClass对象。

s和O(字母大写欧)类型需要单独说一些,因为它们都需要两个载体。我们将在接下来的章节里了解php中对象的具体实现。这样我们改写一下我们在第五章定义的一个函数:

1
<?php
2
function sample_hello_world($name)
3
{
4
    echo "Hello $name!\n";
5
}
6
?>
在编写扩展时,我们需要用zend_parse_parameters()来接收这个字符串:

01
ZEND_FUNCTION(sample_hello_world)
02
{
03
    char *name;
04
    int name_len;
05

06
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",&name, &name_len) == FAILURE)
07
    {
08
        RETURN_NULL();
09
    }
10
    php_printf("Hello ");
11
    PHPWRITE(name, name_len);
12
    php_printf("!\n");
13
}
如果传递给函数的参数数量小于zend_parse_parameters()要接收的参数数量,它便会执行失败,并返回FAILURE。

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

1
<?php
2
function sample_hello_world($name, $greeting)
3
{
4
    echo "Hello $greeting $name!\n";
5
}
6
sample_hello_world('John Smith', 'Mr.');
7
?>
在PHP扩展里应该这样来实现:

01
ZEND_FUNCTION(sample_hello_world)
02
{
03
    char *name;
04
    int name_len;
05
    char *greeting;
06
    int greeting_len;
07
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&name, &name_len, &greeting, &greeting_len) == FAILURE)
08
    {
09
        RETURN_NULL();
10
    }
11
    php_printf("Hello ");
12
    PHPWRITE(greeting, greeting_len);
13
    php_printf(" ");
14
    PHPWRITE(name, name_len);
15
    php_printf("!\n");
16
}
除了上面定义的参数,还有其它的三个参数来增强我们接受参数的能力,如下:

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

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

1
<?php
2
function sample_hello_world($name, $greeting='Mr./Ms.')
3
{
4
    echo "Hello $greeting $name!\n";
5
}
6
sample_hello_world('Ginger Rogers','Ms.');
7
sample_hello_world('Fred Astaire');
8
?>
此时即可以只向sample_hello_world中传递一个参数,也可以传递完整的两个参数。

那同样的功能我们怎样在扩展函数里实现呢?我们需要借助zend_parse_parameters中的(|)参数,这个参数之前的参数被认为是必须的,之后的便认为是非必须的了,如果没有传递,则不会去修改载体。

01
ZEND_FUNCTION(sample_hello_world)
02
{
03
    char *name;
04
    int name_len;
05
    char *greeting = "Mr./Mrs.";
06
    int greeting_len = sizeof("Mr./Mrs.") - 1;
07

08

09
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",
10
      &name, &name_len, &greeting, &greeting_len) == FAILURE) {
11
        RETURN_NULL();
12
    }
13
    php_printf("Hello ");
14
    PHPWRITE(greeting, greeting_len);
15
    php_printf(" ");
16
    PHPWRITE(name, name_len);
17
    php_printf("!\n");
18
}
如果你不传递第二个参数,则扩展函数会被认为默认而不去修改载体。所以,我们需要自己来预先设置有载体的值,它往往是是NULL,或者一个与函数逻辑有关的值。

每个zval,包括IS_NULL型的zval,都需要占用一定的内存空间,并且需要cpu的计算资源来为它申请内存、初始化,并在它们完成工作后释放掉。但是很多代码都都没有意识到这一点。有很多代码都会把一个null型的值包裹成zval的IS_NULL类型,在扩展开发里这种操作是可以优化的,我们可以把参数接收城C语言里的NULL。我们就这一个问题看以下代码:

01
ZEND_FUNCTION(sample_arg_fullnull)
02
{
03
    zval *val;
04
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z",&val) == FAILURE) {
05
        RETURN_NULL();
06
    }
07
    if (Z_TYPE_P(val) == IS_NULL) {
08
        val = php_sample_make_defaultval(TSRMLS_C);
09
    }
10
    ...
11
}
12
ZEND_FUNCTION(sample_arg_nullok)
13
{
14
    zval *val;
15
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!",
16
                                    &val) == FAILURE) {
17
        RETURN_NULL();
18
    }
19
    if (!val) {
20
        val = php_sample_make_defaultval(TSRMLS_C);
21
    }
22
}
这两段代码乍看起来并没有什么很大的不同,但是第一段代码确实需要更多的cpu和内存资源。可能这个技巧在平时并没多大用,不过技多不压身,知道总比不知道好。

Forced Separation

当一个变量被传递给函数时候,无论它是否被引用,它的refcoung__gc属性都会加一,至少成为2。一份是它自己,另一份是传递给函数的的copy。在改变这个zval之前,有时会需要提前把它分成实际意义上的两份copy。这就是"/"格式符的作用。它将把写时复制的zval提前分成两个完整独立的copy,从而使我们可以在下面的代码中随意的对其进行操作。否则我们可能需要不停的提醒自己对接收的参数进行分离等操作。Like the NULL flag, this modifier goes after the type it means to impact. Also like the NULL flag, you won't know you need this feature until you actually have a use for it.

zend_get_arguments()

如果你想让你的扩展能够兼容老版本的PHP,或者你只想以zval*为载体来接收参数,便可以考虑使用zend_get_parameters()函数来接收参数。

zend_get_parameters()与zend_parse_parameters()不同,从名字上我们便可以看出,它直接获取,而不做解析。首先,它不会自动进行类型转换,所有的参数在扩展实现中的载体都需要是zval*类型的,下面让我们来看一个最简单的例子:

01
ZEND_FUNCTION(sample_onearg)
02
{
03
    zval *firstarg;
04
    if (zend_get_parameters(ZEND_NUM_ARGS(), 1, &firstarg)== FAILURE)
05
    {
06
        php_error_docref(NULL TSRMLS_CC, E_WARNING,"Expected at least 1 parameter.");
07
        RETURN_NULL();
08
    }
09
    /* Do something with firstarg... */
10
}
其次,zend_get_parameters()在接收失败的时候,并不会自己抛出错误,它也不能方便的处理具有默认值的参数。

最后一点与zend_parse_parameters不同的是,它会自动的把所有复合copy-on-write的zval进行强制分离,生成一个崭新的copy送到函数内部。如果你希望用它其它的特性,而唯独不需要这个功能,可以去尝试一下用zend_get_parameters_ex()函数来接收参数。

为了不对copy-on-write的变量进行分离操作,zend_get_parameters_ex()的参数是zval**类型的,而不是zval*。 这个函数不太经常用,可能只会在你碰到一些极端问题时候才会想到它,而它用起来却很简单:

1
ZEND_FUNCTION(sample_onearg)
2
{
3
    zval **firstarg;
4
    if (zend_get_parameters_ex(1, &firstarg) == FAILURE)
5
    {
6
        WRONG_PARAM_COUNT;
7
    }
8
    /* Do something with firstarg... */
9
}
注意zend_get_parameters_ex不需要ZEND_NUM_ARGS()作为参数,因为它是在是在后期加入的,那个参数已经不再需要了。

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

可变参数

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

view sourceprint?
01
ZEND_FUNCTION(var_dump)
02
{
03
    int i, argc = ZEND_NUM_ARGS();
04
    zval ***args;
05

06
    args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0);
07
    if (ZEND_NUM_ARGS() == 0 || zend_get_parameters_array_ex(argc, args) == FAILURE)
08
    {
09
        efree(args);
10
        WRONG_PARAM_COUNT;
11
    }
12
    for (i=0; i < argc; i++)
13
    {
14
        php_var_dump(args[i], 1 TSRMLS_CC);
15
    }
16
    efree(args);
17
}
程序首先获取参数数量,然后通过safe_emalloc函数申请了相应大小的内存来存放这些zval**类型的参数。这里使用了zend_get_parameters_array_ex()函数来把传递给函数的参数填充到args中。你可能已经立即想到,还存在一个名为zend_get_parameters_array()的函数,唯一不同的是它将zval*类型的参数填充到args中,并且需要ZEND_NUM_ARGS()作为参数。

http://www.nowamagic.net/librarys/veda/detail/1467
http://www.laruence.com/2009/04/28/719.html
分享到:
评论

相关推荐

    flutter_parse:用于Parse Platform Mobile和Web SDK的Flutter混合插件

    使用纯Dart管理Parse SDK的软件包。 特征: 解析ACL 解析配置 解析文件 解析对象 解析查询 解析角色 解析会话 ParseUser 安装 添加到pubspec.yaml: dependencies : flutter_parse : ^0.2.3 导入库 import '...

    hive函数大全(中文版)

    14. URL解析函数:parse_url 26 15. json解析函数:get_json_object 27 16. 空格字符串函数:space 27 17. 重复字符串函数:repeat 27 18. 首字符ascii函数:ascii 28 19. 左补足函数:lpad 28 20. 右补足函数:rpad...

    Python库 | dbc_parse-1.0.7-py3-none-any.whl

    资源分类:Python库 所属语言:Python 资源全名:dbc_parse-1.0.7-py3-none-any.whl 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES

    主要介绍了IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

    实现类似av_parser_parse2功能

    刚开始解码H264视频时,查阅文档说有Nalu头,参考雷神代码,将文件指针传入去掉头可以解码,但是需要每次传入定量buffer解码,不是传入文件指针,经过修改可以用有bug

    Python库 | xstavka_parse_package-0.25.tar.gz

    资源分类:Python库 所属语言:Python 资源全名:xstavka_parse_package-0.25.tar.gz 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    Android安装应用 INSTALL_FAILED_DEXOPT 问题及解决办法

    今天在帮助客户解决一个问题时,由于他们的手机是用的5.0系统身边没有5.0系统的手机,只能用一个模拟器来安装测试应用,但是在安装过程中碰到了以下问题: The application could not be installed: INSTALL_FAILED...

    JSON2.JS JSON.JS JSON_PARSE.JS

    method and a parse method. The parse method uses the eval method to do the parsing, guarding it with several regular expressions to defend against accidental code execution hazards. On current ...

    简单谈谈PHP中strlen 函数

    strlen函数说明。 int strlen ( string $string ) ... if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s1, &s1_len) == FAILURE) { return; } RETVAL_LONG(s1_len); } 该文章讲到,该函数很简

    STM32学习笔记之Cjson的使用

    需要用到的几个函数: 1、解析JSONJ结构得到cjson对象:cJSON * root=cJSON_Parse(char *buf); 2、获取无格式的json对象:cJSON_PrintUnformatted(cJSON *item) 3、根据键值获取对应的值:cJSON *cJSON_...

    Sybase.PowerDesigner.Shell.dll

    Sybase.PowerDesigner.Shell

    Python库 | draco_parse-0.1-py3-none-any.whl

    资源分类:Python库 所属语言:Python 资源全名:draco_parse-0.1-py3-none-any.whl 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    Python库 | damask_parse-0.1.5-py3-none-any.whl

    资源分类:Python库 所属语言:Python 资源全名:damask_parse-0.1.5-py3-none-any.whl 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    command_line_parse_xml

    command_line_parse_xml

    Zend Framework教程之Zend_Config_Ini用法分析

    本文实例讲述了Zend Framework教程之Zend_Config_Ini用法。分享给大家供大家参考,具体如下: Zend_Config_Ini允许开发者通过嵌套的对象属性语法在应用程序中用熟悉的 INI 格式存储...PHP 函数。请复习这个文档了解它的

    cvs_java_parse.rar

    cvs_java_parse.rar cvs_java_parse.rar

    xmlparse(xml与PHP数组相互转换)封装函数

    xmlparse.php为封装好的xml与php数组相互转换的函数: XML-&gt;PHP: parse_xml_config(); PHP-&gt;XML: dump_xml_config(); #将转换好的XML放入文件: file_put_contents_safe().

Global site tag (gtag.js) - Google Analytics