在开发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