函数的参数则是PHP代码层和C代码层之间交换数据的唯一途径,因为PHP的调用语法是动态的,不会做任何错误检查,所以检查参数工作需要交给开发PHP扩展人员完成,看一下我们在快速入门里面的一段代码,使用zend_parse_parameters来解析参数:
PHP_FUNCTION(say_goodbye) { 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, "Goodbye %s\n", arg); RETURN_STRINGL(strg, len, 0); }
获取参数数量
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,关于参数解析其实还有更复杂的内容,下篇再介绍。