• 冒险村物语
  • 英雄无敌3塔防
  • 驾考家园手游

php扩展与嵌入--php扩展的参数

2014-09-05

之前的文章中,函数在接收的参数和返回的类型上都比较简单,但是往往实际中所遇到的都更加复杂一些。这篇文章主要说一下如何在php扩展开发中接收来自于用户空间的参数,并且对这些参数的类型、个数等信息进行相应的检查。


1. 使用zend_parse_parameters()进行自动的类型转换

在php的扩展中,最容易的得到输入参数的方法就是使用zend_parse_parameters()函数。

对这个函数的调用的第一个参数总是:ZEND_NUM_ARGS() TSRMLS_CC. 这个参数返回一个int型的输入参数的数目。
第二个参数是format参数,是由字符串类型组成,分别对应着不同的Zend Engine支持的类型。
下图中给出了format参数可能具有的类型:/
而接下来的参数取决于之前所请求的类型。对于比较简单的类型来说,这个参数一般都是取引用的基元,如下例所示:
PHP_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 you "             "passed is: %ld/n", foo);    RETURN_TRUE;}
这里是l也就是long类型,所以相对应的提前声明了一个long foo参数,然后通过引用的方式把值传递了进来。下面给出更加详细的参数与c语言中的类型的对应关系:b ------ zend_booll ------- longd ------- doubles ------- char* , intr ------- zval*a ------ zval*o ------ zval*O ----- zval*, zend_class_entry*z ------ zval*Z ----- zval**
注意到对于复杂的类型采用的是简单的zval*类型。这跟返回复杂类型的时候没有RETURN_*的道理是一样的。ZPP所做的事情是保证所接收到的zval*是正确的类型。如果必要的话,它也会执行隐式的转换,比如把数组转成stdClass对象。
对于s类型来说,它比较特殊,一个char*一个int,这个还是主要因为php里面字符串的特殊结构:
function sample_hello_world($name) {    echo "Hello $name!/n";}
在c语言中,要使用的就是zend_parse_parameters函数了:
PHP_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会从左到右去提取这些参数:
function sample_hello_world($name, $greeting) {    echo "Hello $greeting $name!/n";}sample_hello_world('John Smith', 'Mr.');
Or:
PHP_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可选的参数: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');

在C的解释中,有类似的实现方式:
PHP_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/0


IS_NULL VS NULL:
每一个zval的类型,即使是最简单的IS_NULL类型,都占据一定的内存空间,同时也需要时间去申请和释放它们。所以很多时候没有必要使用这个类型,下面两段代码里面给出了对比:

PHP_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) {//使用zval检查为空的方式        val = php_sample_make_defaultval(TSRMLS_C);    }...PHP_FUNCTION(sample_arg_nullok){    zval *val;    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!",                                    &val) == FAILURE) {        RETURN_NULL();    }    if (!val) {// c语言风格的检查为空的方式        val = php_sample_make_defaultval(TSRMLS_C);    }...

Forced Seperation强制分离:

当一个变量传入函数的时候,不管是不是用传引用的方式,refcount总是至少为2.一个是本身,一个是传进函数的拷贝。在对这个zval进行更改之前,把它从一个非引用的集合中分离出来是很必要的。
使用/会很方便,它会自动的把任何copy-on-write引用(也就是假引用的)的变量分离出来。
这个特性跟NULL标志位一样,需要的时候才用到。


zend_get_parameters():
如果想要兼容老版本的php或只想以zval作为载体来接收参数,那么可以考虑使用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... */}

同时,它在接收失败的时候不会自己抛出错误,也不能处理具有默认值的参数,最后一点跟parse不同的地方在于它会自动的把所有符合copy-on-write的zval进行强制分离,生成一个崭新的拷贝送到函数内部。
如果不需要这个功能可以用zend_get_parameters_ex()它的参数是zval**的

ZEND_FUNCTION(sample_onearg) {    zval **firstarg;    if (zend_get_parameters_ex(1, &firstarg) == FAILURE) {        WRONG_PARAM_COUNT;抛出一个E_WARNING级别的错误信息,并自动return。    }    /*



可变参数,处理任意数目的参数:

还有两种zend_get_parameters_**函数,专门用来解决很多或者无法提前知道参数数目的情况。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
程序首先获取参数数量,然后通过safe_emalloc函数申请相应大小的内存来存储这些zval**的参数。这里使用zend_get_parameters_array_ex()函数来把传递给函数的参数填充到args中。提醒一下,还存在一个zend_get_parameters_array()函数,唯一不同是它将zval*类型的参数填充到args中,并且需要ZEND_NUM_ARGS()作为参数。



2. Arg info参数和类型的绑定

这个arg info结构是ZE2才有的。每一个arg info声明都由一个ZEND_BEGIN_ARG_INFO()或ZEND_BEGIN_ARG_INFO_EX()宏组成,后面跟着0个或多个ZEND_ARG_*INFO(), 然后最后以ZEND_END_ARG_INFO()作为结尾。
假定要重写count()函数:

PHP_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_parameter()的话,那么就需要自己在函数内部内建类型检查。除非使用类型绑定:

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就会帮你进行类型检查了。同时还给了参数一个名字,从而使得产生的错误信息更加具有可读性。

而对于对象来说,也可以通过arg info进行限定:

ZEND_BEGIN_ARG_INFO(php_sample_class_arginfo, 0)         ZEND_ARG_OBJECT_INFO(1, "obj", "stdClass", 0)     ZEND_END_ARG_INFO()

这里第一个参数被设为1,表示是引用方式传递,但是对象其实在ZE2中都是引用传递的。不要忘记了array和object的allow_null选项。
如果使用的是php4的话,只能用PHP_TYPE_P()进行检查,或使用convert_to_type()方法进行类型转换。









(免责声明:文章内容如涉及作品内容、版权和其它问题,请及时与我们联系,我们将在第一时间删除内容,文章内容仅供参考)

人气推荐

知识阅读

精彩推荐

  • 游戏
  • 软件
查看更多>>