前言经过上一文 php扩展之扩展框架的自动生成 ,对php扩展框架的整体了解,基本上可以说,对于扯淡如何写php扩展和关键点有了一定的把握,但关键的还是在于如何写PHP_FUNCTION的函数。 本文主要记录一下,php在调用扩展的时候进行传参,那么扩展函数是怎么接招的。当作自己的备忘录 正文1.zend_parse_parameters获取函数传递的参数,可以使用zend_parse_parameters函数,细心的同学会发现官方生成的默认的函数也是用这个函数来接收参数的。 这个函数怎么用?首先可以把这个看书当作php的scanf一样使用。(这个函数不熟悉的自觉这里) zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, &参数1,&参数2…); 第一个参数是传递给函数的参数个数。通常的做法是传给它ZEND_NUM_ARGS()。(ZEND_NUM_ARGS() 来表示对传入的参数“有多少要多少”)这是一个表示传递给函数参数总个数的宏。 第二个参数是为了线程安全,总是传递TSRMLS_CC宏。 第三个参数是一个字符串,指定了函数期望的参数类型,后面紧跟着需要随参数值更新的变量列表。由于PHP是弱类型语言,采用松散的变量定义和动态的类型判断,而c语言是强类型的,而zend_parse_parameters()就是为了把不同类型的参数转化为期望的类型。(当实际的值无法强制转成期望的类型的时候,会发出一个警告) 第四第五直到第n个参数,都是要传进来的值的数值。 第三个参数详解关于第三个参数,这里提供一个供选择的参数列表: | 类型指定符 | 对应的C类型 | 描述 |
|---|
| l | long | 符号整数 | | d | double | 浮点数 | | s | char *, int | 二进制字符串,长度 | | b | zend_bool | 逻辑型(1或0) | | r | zval * | 资源(文件指针,数据库连接等) | | a | zval * | 联合数组 | | o | zval * | 任何类型的对象 | | O | zval * | 指定类型的对象。需要提供目标对象的类类型 | | z | zval * | 无任何操作的zval |
这边有两点需要特别注意 1.这里的字符串类型对应的c类型有两个参数。是的。这说明在使用zend_parse_parameters()函数的时候需要这么使用: zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",&name, &name_len) 一个表示字符串内容,另一个是字符串长度。 2.需要大概知道一下,zval是啥? zval是Zend引擎的值容器。无论这个变量是布尔型,字符串型或者其他任何类型,其信息总会包含在一个zval联合体中。来一波zval的结构: typedef union _zval { long lval; double dval; struct { char *val; int len;
} str;
HashTable *ht;
zend_object_value obj;
} zval;ps:关于typedef就不解释了,因为我发现有人已经写的非常好了,请点击关于typedef的用法总结 这边还涉及到额外的三个用法,来增强我们接收参数的能力: | 符号 | 解释 |
|---|
| | | 它之前的参数都是必须的,之后的都是非必须的,也就是有默认值的。 | | ! | 如果接收了一个PHP语言里的null变量,则直接把其转成C语言里的NULL,而不是封装成IS_NULL类型的zval。 | | / | 如果传递过来的变量与别的变量共用一个zval,而且不是引用,则进行强制分离,新的zval的is_ref__gc==0, and refcount__gc==1. |
“|”和”!”将在下文有具体例子讲解,关于”/”虽然大概懂意思,但没想到具体的例子。 走一波与php的交互正常的样子 在PHP中 <?phpfunction my_function($msg) {
echo "我收到参数啦: {$msg}!
";
}
my_function('咖啡色的羊驼');如果my_function写成PHP扩展: ZEND_FUNCTION(my_function) {
char *msg;
int msg_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",&msg, &msg_len) == FAILURE){
RETURN_NULL();
}
php_printf("我收到参数啦:");
PHPWRITE(msg, msg_len);
php_printf("!
");
}两个参数的样子 在PHP中 <?phpfunction my_function($email, $msg) {
echo "我收到参数啦: {$email}、 {$msg}!
";
}
my_function('123456@qq.com', '咖啡色的羊驼');如果my_function写成PHP扩展: ZEND_FUNCTION(my_function) {
char *email;
int email_len;
char *msg;
int msg_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&msg, &msg_len,&email, &email_len) == FAILURE){
RETURN_NULL();
}
php_printf("我收到参数啦:");
PHPWRITE(email, email_len);
PHPWRITE(msg, msg_len);
php_printf("!
");
}两个参数,其中一个可选且有默认值 <?phpfunction my_function($email, $msg = '咖啡色的羊驼') {
echo "我收到参数啦: {$email}、 {$msg}!
";
}
my_function('123456@qq.com');如果my_function写成PHP扩展: ZEND_FUNCTION(my_function) {
char *email;
int email_len;
char *msg = "咖啡色的羊驼";
int msg_len = sizeof("咖啡色的羊驼") - 1;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",&msg, &msg_len,&email, &email_len) == FAILURE){
RETURN_NULL();
}
php_printf("我收到参数啦:");
PHPWRITE(email, email_len);
PHPWRITE(msg, msg_len);
php_printf("!
");
}这里说明了”|”的使用 参数为null时,省内存的写法 先来不省的写法 ZEND_FUNCTION(my_function) {
zval *val;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z",&val) == FAILURE) {
RETURN_NULL();
}
}省内存 ZEND_FUNCTION(my_function) {
zval *val;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!",&val) == FAILURE) {
RETURN_NULL();
}
}这里例子用上了”!”,每个zval,包括IS_NULL型的zval,都需要占用一定的内存空间,需要cpu的计算资源来为它申请内存、初始化,并在它们完成工作后释放掉。但是很多代码都都没有意识到这一点。有很多代码都会把一个null型的值包裹成zval的IS_NULL类型,在扩展开发里这种操作是可以优化的,我们可以把参数接收成C语言里的NULL。 所以就差了一个!,第二个例子就更省了内存。 2.zend_get_arguments()用法例子是最好的用法讲解,上例子: ZEND_FUNCTION(my_function) {
zval *email; if (zend_get_parameters(ZEND_NUM_ARGS(), 1, &email)== FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,"至少需要一个参数");
RETURN_NULL();
}
// ... }区别与特点1.能够兼容老版本的PHP,并且只以zval为载体来接收参数。 2.直接获取,而不做解析,不会进行类型转换,所有的参数在扩展实现中的载体都需要是zval类型的。 3.接受失败的时候,不会自己抛出错误,也不能方便的处理有默认值的参数。 4.会自动的把所有符合copy-on-write的zval进行强制分离,生成一个崭新的copy送到函数内部。 综合评价:还是用zend_parse_parameters吧,这个函数了解下即可,不给力。 3.zend_get_parameters_ex()用法ZEND_FUNCTION(my_function) {
zval **msg; if (zend_get_parameters_ex(1, &msg) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,"至少需要一个参数");
RETURN_NULL();
}
// ...}不需要ZEND_NUM_ARGS()作为参数,因为它是在是在后期加入的,那个参数已经不再需要了。 区别与特点1.此函数基本同zend_get_parameters()。 2.唯一不同的是它不会自动的把所有符合copy-on-write的zval进行强制分离,会用到老的zval的特性 综合评价:极端情况下可能会用到,这个函数了解下即可。 zend_get_parameter_**这个包括:zend_get_parameters_array_ex()和zend_get_parameters_array() 用法ZEND_FUNCTION(var_dump) {
int i, argc = ZEND_NUM_ARGS();
zval ***args;
args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0);
if (ZEND_NUM_ARGS() == 0 || zend_get_parameters_array_ex(argc, args) == FAILURE) {
efree(args);
WRONG_PARAM_COUNT;
}
for (i=0; i<argc; i++) {
php_var_dump(args[i], 1 TSRMLS_CC);
}
efree(args);
}这个有点复杂,需要解释一下: 程序首先获取参数数量,然后通过safe_emalloc函数申请了相应大小的内存来存放这些zval**类型的参数。这里使用了zend_get_parameters_array_ex()函数来把传递给函数的参数填充到args中。 是的 这个参数专门用于解决像php里面的var_dump的一样,可以无限传参数进去的函数的实现 区别与特点1.用于应对无限参数的扩展函数的实现。 2.zend_get_parameters_array与zend_get_parameters_array_ex唯一不同的是它将zval*类型的参数填充到args中,并且需要ZEND_NUM_ARGS()作为参数。 综合评价:当遇到确实需要处理无限参数的时候,真的要用这个函数了。zend_parse_parameters真的做不到啊~ 总结抛开一切,最少也要学会zend_parse_parameters()的用法。好的。扩展函数传参数的技能get了。 |