在开发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
最新评论: