略微加速

略速 - 互联网笔记

PHP扩展开发-参数接收

2016-05-19 leiting (3531阅读)

标签 PHP

在开发PHP扩展的时候, 当我们传递参数给函数的时候, 参数也是要声明的, 并通过zend_FE宏将函数与参数关联起来, 注册到函数表中。

对于之前的php_hello_world.dll的例子。

我们对于 函数say_hello函数有传递一个name的参数。

对于 这个name的定义如下:

ZEND_BEGIN_ARG_INFO(arg_say_hello, 0)  
ZEND_ARG_INFO(0, name)  
ZEND_END_ARG_INFO()

实际上这是一个宏定义, 我们来看一个宏的具体 内容

#define ZEND_ARG_INFO(pass_by_ref, name) \
    { #name, sizeof(#name)-1, NULL, 0, 0, 0, pass_by_ref, 0, 0 }, #  
#define ZEND_ARG_PASS_INFO(pass_by_ref) \
    { NULL, 0, NULL, 0, 0, 0, pass_by_ref, 0, 0 },  
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) 
    { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, 0, allow_null, pass_by_ref, 0, 0 },  
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) 
    { #name, sizeof(#name)-1, NULL, 0, 1, allow_null, pass_by_ref, 0, 0 },  
#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \  
    zend_arg_info name[] = { \  
    { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },  
#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \  
        static const zend_arg_info name[] = { \  
                { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },  
#define ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference) \  
    ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO() \
        };

以ZEND_BEGIN_ARG_INFO宏定义开始,以ZEND_END_ARG_INFO()结束,这两个宏定义解释如下:

ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference): 
    开始参数块定义,pass_rest_by_reference为1时,强制所有参数为引用类型 
ZEND_END_ARG_INFO() : 
    结束参数块定义

而每一个参数的定义可以是下列宏定义中的一个:

ZEND_ARG_INFO声明普通参数
ZEND_ARG_OBJ_INFO声明对象类型的参数
ZEND_ARG_ARRAY_INFO声明数组类型的参数
ZEND_ARG_PASS_INFO(pass_by_ref)pass_by_ref为1时,强制设置后续的参数为引用类型

 

所以 对于上面arg_say_hello参数的定义, 展开之后就是

static const zend_arg_info arg_user_login[] = { 
    { NULL, 0, NULL, 0, 0, 0, 0, 0, 0 },
    { "name", sizeof(“name“)-1, NULL, 0, 0, 0, 0, 0, 0 }, 
}

可以看到,其实我们定义参数信息展开后就是一个zend_arg_info结构数组,zend_arg_info结构定义如下:

typedef struct _zend_arg_info {  
    char *name;  
    zend_uint name_len;  
    char *class_name;  
    zend_uint class_name_len;  
    zend_bool array_type_hint;  
    zend_bool allow_null;  
    zend_bool pass_by_reference;  
    zend_bool return_reference;  
    int required_num_args;  
} zend_arg_info;

下面对各个字段做一解释:

name参数名称
name_len参数名称字符串长度
class_name当参数类型为类时,指定类名称
class_name_len类名称字符串长度
array_type_hint标识参数类型是否为数组
allow_null是否允许设置为空
pass_by_reference是否设置为引用,即使用&操作符
return_reference标识函数将重写return_value_ptr,后面介绍函数返回值时再做介绍
required_num_args设置函数被调用时,传递参数至少为前N个,当设置为-1时,必须传递所有参数
zend_function_entry hello_world_functions[] = {  
    //PHP_FE(confirm_hello_world_compiled,  NULL)       /* For testing, remove later. */  
    PHP_FE(say_hello,arg_say_hello)  
    /* __function_entries_here__ */  
    {NULL, NULL, NULL}  /* Must be the last line in hello_world_functions[] */  
};

然后通过Zend提供的PHP_FE将函数与参数关联起来。

 

下面我们再看看函数是怎么接收这个参数的

 

函数的参数则是PHP代码层和C代码层之间交换数据的唯一途径,因为PHP的调用语法是动态的,不会做任何错误检查,所以检查参数工作需要交给开发PHP扩展人员完成。

 

还是对于 say_hello 的例子,我们使用使用zend_parse_parameters来解析参数:

PHP_FUNCTION(say_hello)  
{  
    char *arg = NULL;   
    int arg_len, len;   
    char *strg;  
      
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",  
        &arg, &arg_len) == FAILURE) {   
        return;   
    }  
      
    len = spprintf(&strg, 0, "Hello %s/n", arg);   
    RETURN_STR

*ps : TSRMLS_CC

获取参数数量

PHP无法根据函数的显式声明来对调用进行语法检查,而且它还支持可变参数,所以我们就不得不在所调用函数的内部来获取参数个数。我们可以使用宏ZEND_NUM_ARGS来获取参数个数,如下面的代码:

if(ZEND_NUM_ARGS() != 2)  
{  
    WRONG_PARAM_COUNT  
}

这段代码使用宏WRONG_PARAM_COUNT抛出一个参数个数错误。

解析参数

开发PHP扩展时,解析参数基本是使用标准方式zend_parse_parameters,使用方式如下所示:

int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...);

num_args参数指定参数个数,前面介绍过使用ZEND_NUM_ARGS获取参数个数;TSRMLS_DC宏指定线程安全;type_spec参数 是一个字符串,指定各个参数的类型,每个参数类型用一个字母表示。如果成功地解析和接收到了参数并且在转换期间也没出现错误,那么这个函数就会返回 SUCCESS,否则返回FAILURE,各个参数类型的字母标识如下:

 

l - 长整数 
d - 双精度浮点数 
s - 字符串 (也可能是空字节)和其长度 
b - 布尔值 
r - 资源, 保存在 zval* 
a - 数组, 保存在 zval* 
o - (任何类的)对象, 保存在 zval* 
O - (由class entry 指定的类的)对象, 保存在 zval* 
z - 实际的 zval*

 

在设置参数类型时,还可以使用以下几个特殊的字符:

| - 表明剩下的参数都是可选参数。如果用户没有传进来这些参数值,那么这些值就会被初始化成默认值。 
/ - 表明参数解析函数将会对剩下的参数以 SEPARATE_ZVAL_IF_NOT_REF() 的方式来提供这个参数的一份拷贝,除非这些参数是一个引用。 
! - 表明剩下的参数允许被设定为 NULL(仅用在 a、o、O、r和z身上)。如果用户传进来了一个 NULL 值,则存储该参数的变量将会设置为 NULL。

 

来看几个例子

/* 取得一个长整数,一个字符串和它的长度,再取得一个 zval 值。 */  
long l;  
char *s;  
int s_len;  
zval *param;  
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lsz", &l, &s, &s_len, ¶m) == FAILURE) {  
   return;  
}  
/* 取得一个由 my_ce 所指定的类的一个对象,另外再取得一个可选的双精度的浮点数。 */  
zval *obj;  
double d = 0.5;  
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|d", &obj, my_ce, &d) == FAILURE) {  
   return;  
}  
/* 取得一个对象或空值,再取得一个数组。如果传递进来一个空对象,则 obj 将被设置为 NULL。*/  
zval *obj;  
zval *arr;  
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O!a", &obj, &arr) == FAILURE) {  
   return;  
}  
/* 取得一个分离过的数组。*/  
zval *arr;  
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/", &arr) == FAILURE) {  
   return;  
}  
/* 仅取得前 3 个参数(这对可变参数的函数很有用)。*/  
zval *z;  
zend_bool b;  
zval *r;  
if (zend_parse_parameters(3, "zbr!", &z, &b, &r) == FAILURE) {  
   return;  
}

在接收参数时还有一个可用的函数zend_parse_parameters_ex,允许我们传入一些flags来控制解析参数的动作,使用方式如下所示:

int zend_parse_parameters_ex(int flags, int num_args TSRMLS_DC, char *type_spec, ...);

目前flags仅能传入ZEND_PARSE_PARAMS_QUIET这个值,表示函数不输出任何错误信息,如下面的示例:

long l1, l2, l3;  
char *s;  
if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,  
                            ZEND_NUM_ARGS() TSRMLS_CC,  
                            "lll", &l1, &l2, &l3) == SUCCESS) {  
   /* manipulate longs */  
} else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,  
                                   ZEND_NUM_ARGS(), "s", &s, &s_len) == SUCCESS) {  
   /* manipulate string */  
} else {  
   php_error(E_WARNING, "%s() takes either three long values or a string as argument",  
             get_active_function_name(TSRMLS_C));  
   return;  
}

可变参数

由于PHP支持可变参数,所以在接收可变参数时,使用前面介绍的两个方法就不太合适,我们可以用zend_get_parameters_array_ex()来代替,如下面的示例:

zval **parameter_array[4];  
/* 取得参数个数 */  
argument_count = ZEND_NUM_ARGS();  
/* 看一下参数个数是否满足我们的要求:最少 2 个,最多 4个。 */  
if(argument_count < 2 || argument_count > 4)  
{  
    WRONG_PARAM_COUNT;  
}  
/* 参数个数正确,开始接收。 */  
if(zend_get_parameters_array_ex(argument_count, parameter_array) != SUCCESS)  
{  
    WRONG_PARAM_COUNT;  
}

在PHP扩展中接收参数,总共有三个函数:zend_parse_parameters、zend_parse_parameters_ex、zend_get_parameters_array_ex

 

之前有看到zval的使用, 那这个是什么结构体呢?

 

其定义如下:

typedef pval zval;  
typedef struct _zval_struct zval;  
struct _zval_struct {  
    /* Variable information */  
    zvalue_value value; /* value */  
    unsigned char type; /* active type */  
    unsigned char is_ref;  
    short refcount;  
};

zval结构的定义使用了C语言中的联合类型,各个字段说明如下:

1. value    变量内容的联合

    zvalue_value结构定义

typedef union _zvalue_value {  
    long lval; /* long value */  
    double dval; /* double value */  
    struct {  
        char *val;  
        int len;  
    } str;  
    HashTable *ht; /* hash table value */  
    struct {  
        zend_class_entry *ce;  
        HashTable *properties;  
    } obj;  
} zvalue_value;

zvalue_value结构的说明如下:

           lval    如果变量类型为 IS_LONG、IS_BOOLEAN 或 IS_RESOURCE 就用这个属性值 
           dval    如果变量类型为 IS_DOUBLE 就用这个属性值 
           str    如果变量类型为 IS_STRING 就访问这个属性值。它的字段 len 表示这个字符串的长度,字段 val 则指向该字符串。

               Zend 使用的是 C 风格的字符串,因此字符串的长度就必须把字符串末尾的结束符 0×00 也计算在内 
            ht    如果变量类型为数组,那这个 ht 就指向数组的哈希表入口 
            obj    如果变量类型为 IS_OBJECT 就用这个属性值

 

2.type    变量的类型

 

   变量类型定义: 
 IS_NULL    表示是一个空值 NULL 
IS_LONG    是一个(长)整数 
IS_DOUBLE    是一个双精度的浮点数 
IS_STRING    是一个字符串 
IS_ARRAY    是一个数组 
IS_OBJECT    是一个对象 
IS_BOOL    是一个布尔值 
IS_RESOURCE    是一个资源(关于资源的讨论,我们以后会在适当的时候讨论到它) 
IS_STRING    是一个常量

 

 

3. is_ref   

      0 表示这个变量还不是一个引用。1 表示这个变量还有被别的变量所引用

 

4. refcount   

    表示这个变量是否仍然有效。每增加一个对这个变量的引用,这个数值就增加 1。反之,每失去一个对这个变量的引用,该值就会减1。当引用计数减为0的时候,就说明已经不存在对这个变量的引用了,于是这个变量就会自动释放

 

给定一个具体的zval,可用三个便利的宏中的一个测试它的类型:Z_TYPE(zval)、Z_TYPE_P(zval*)或Z_TYPE_PP(zval**)。三者之间仅有的功能上的区别在于传入的变量所期望的间接的级别。如下面的示例:

PHP_FUNCTION(hello_type)  
{  
    zval *uservars;  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &uservars) == FAILURE)  
    {  
        RETURN_NULL();  
    }  
    switch (Z_TYPE_P(uservars))  
    {  
    case IS_NULL:  
        php_printf("NULL/n");  
        break;  
    case IS_BOOL:  
        php_printf("Boolean:%s/n",Z_LVAL_P(uservars)?"TRUE":"FALSE");  
        break;  
    case IS_LONG:  
        php_printf("Long is %ld/n",Z_LVAL_P(uservars));  
        break;  
    case IS_DOUBLE:  
        php_printf("Long is %f/n",Z_LVAL_P(uservars));  
        break;  
    case IS_STRING:  
        php_printf("String:");  
        PHPWRITE(Z_STRVAL_P(uservars),Z_STRLEN_P(uservars));  
        php_printf("/n");  
        break;  
    case IS_RESOURCE:  
        php_printf("Resource/n");  
        break;  
    case IS_ARRAY:  
        php_printf("Array/n");  
        break;  
    case IS_OBJECT:  
        php_printf("Object/n");  
        break;  
    default:  
            php_printf("Unknown/n");  
    }  
}

可以看到如下的输出。

 

 

 

在PHP扩展中对于用户传过来的参数,本质上都是一个zval结构,我们需要调用一些转换函数进行强制类型转换(zend_parse_parameters函数会对基本类型做转换),Zend引擎提供了convert_to_xxx系列函数帮助我们进行类型转换:

convert_to_boolean_ex() 
    强制转换为布尔类型。若原来是布尔值则保留,不做改动。长整型值0、双精度型值0.0、空字符串或字符串‘0’还有空值 NULL 都将被转换为 FALSE(本质上是一个整数 0)。数组和对象若为空则转换为 FALSE,否则转为 TRUE。除此之外的所有值均转换为 TRUE(本质上是一个整数 1)。

convert_to_long_ex() 
    强制转换为长整型,这也是默认的整数类型。如果原来是空值NULL、布尔型、资源当然还有长整型,则其值保持不变(因为本质上都是整数 0)。双精度型则被简单取整。包含有一个整数的字符串将会被转换为对应的整数,否则转换为 0。空的数组和对象将被转换为 0,否则将被转换为 1。

convert_to_double_ex() 
    强制转换为一个双精度型,这是默认的浮点数类型。如果原来是空值 NULL 、布尔值、资源和双精度型则其值保持不变(只变一下变量类型)。包含有一个数字的字符串将被转换成相应的数字,否则被转换为 0.0。空的数组和对象将被转换为 0.0,否则将被转换为 1.0。

convert_to_string_ex() 
    强制转换为数组。若原来就是一数组则不作改动。对象将被转换为一个以其属性为键名,以其属性值为键值的数组。(方法强制转换为字符串。空值 NULL 将被转换为空字符串。布尔值 TRUE 将被转换为 ‘1’,FALSE 则被转为一个空字符串。长整型和双精度型会被分别转换为对应的字符串,数组将会被转换为字符串‘Array’,而对象则被转换为字符串‘Object’。

convert_to_array_ex(value) 
    强制转换为数组。若原来就是一数组则不作改动。对象将被转换为一个以其属性为键名,以其属性值为键值的数组。(方法将会被转化为一个‘scalar’键, 键值为方法名)空值 NULL 将被转换为一个空数组。除此之外的所有值都将被转换为仅有一个元素(下标为 0)的数组,并且该元素即为该值。

convert_to_object_ex(value) 
    强制转换为对象。若原来就是对象则不作改动。空值 NULL 将被转换为一个空对象。数组将被转换为一个以其键名为属性,键值为其属性值的对象。其他类型则被转换为一个具有‘scalar’属性的对象,‘scalar’属性的值即为该值本身。

convert_to_null_ex(value) 
    强制转换为空值 NULL。

http://blog.csdn.net/php_boy/article/details/6456761

http://blog.csdn.net/siren0203/article/details/7506177


北京半月雨文化科技有限公司.版权所有 京ICP备12026184号-3