您的位置:首页 > 编程语言 > PHP开发

PHP Windows 扩展的开发(2) 参数

2011-05-31 14:06 579 查看
在开发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(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


获取参数数量

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: