PHP内核介绍及扩展开发指南——Extensions 的编写:使用参数

2012-11-28 12:17 阅读 680 次 评论关闭

目录

一、基础知识

PHP内核介绍及扩展开发指南——基础知识:PHP变量的存储

PHP内核介绍及扩展开发指南——基础知识:HashTable结构

二、Extensions 的编写

PHP内核介绍及扩展开发指南——Extensions 的编写:Hello World

 

函数的一个重要部分就是访问参数,但由于extension的特殊性,我们无法像通常的函数那样来访问参数。

先来看导出C函数的原型:

 

void zif_first_module (

int ht,

zval * return_value,

zval **return_value_ptr,

zval * this_ptr,

int return_value_used

);

 

ht是用户传入参数的数目,但一般不应直接读取,而是通过宏ZEND_NUM_ARGS()来获取,这通常用于判断用户是否传入了规定数目的参数。下面介绍如何在我们的C函数中访问这些参数。

2.2.1   标准方法

常用的方法是使用下面这个函数,其使用方法类似于scanf,采用格式化字符串和变长参数列表的方式:

int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...);

num_args指出我希望获取的参数数目,通常使用ZEND_NUM_ARGS(),因为我们一般会先用ZEND_NUM_ARGS()判断用户是否传入了规定数目的参数。TSRMLS_DC宏用于线程安全,define和declare时必须这样填写,在调用时应该改用TSRMLS_CC。

type_spec是格式化字符串,其每个字符代表期望的当前参数的类型,之后应传递相应类型变量的指针来接收值,就像scanf那样,可用的字符如下:

 

格式字符

PHP参数类型

接收变量类型

l

long long

d

double double

s

string char*和int

b

boolean zend_bool

r

resource zval*

a

array zval*

z

zval zval*

o/O/C

类,不予讨论 N/A

 

这里面,string是个特例,它需要两个参数,分别获取字符串指针和长度,这是因为PHP没有采用C串,不能根据0来判断字符串结尾。下面是个示例程序:

// 获取一个long、一个string和一个resource

long l;
char *s;        // 字符串地址
int s_len;      // 字符串长度
zval *res;

 

// 检查参数数目

if(ZEND_NUM_ARGS() != 3)

WRONG_PARAM_COUNT; // 该宏输出相应错误信息并退出当前函数
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
                         "lsr", &l, &s, &s_len, &res) == FAILURE)

 return;

由于PHP语法不能规定函数原型,因此用户可以传递任意类型的参数,对此,zend_parse_parameters自动进行了类型检查和转换:在内置标量类型,即long、double、boolean和string之间,Zend会自动进行类型转换,我们总能成功取得参数;resource和array则不进行转换,用户传入的参数必须具有指定类型,否则返回错误;zval作为通用结构,可以用于任何参数类型,Zend只需要简单的将其写入本地的接收变量。

除了类型格式符外,该函数还支持另外3个控制符:

格式字符

意义

|

后面的参数是可选的,如果用户没有传递相应的参数,则本地接收变量保持不变,这用于支持默认参数;

!

前面的那个参数可以是NULL,仅用于razoOC,如果用户传递的是NULL,则本地的接收zval*被设为NULL;

/

如果前面那个参数不是引用传递的,则不直接使用传入的zval,而是执行Copy-On-Write。这一点将在后面解释。

 

最后,关于参数的数目也是有要求的。如果没有采用默认参数,即’|’格式符,则ZEND_NUM_ARGS()、num_args和格式串指出的参数数目这三者间必须完全匹配,否则zend_parse_parameters返回错误;如果使用了默认参数,则ZEND_NUM_ARGS()应和num_args相等,并且应该落在格式串指出的参数数目区间内。

2.2.2   底层方法

大部分情况下,使用标准方法就可以了,但有些函数可能需要处理变参,标准方法对此无能为力(*)。此时,只有使用更加原始的方法——直接获取zval。Zend提供了如下的API:

int zend_get_parameters_array_ex(

int param_count,

zval ***argument_array

TSRMLS_DC);

param_count是希望获取的参数数目,这个值不得大于ZEND_NUM_ARGS(),否则函数出错。argument_array是一个zval**类型的数组,用于接收参数。

这个函数只是简单的返回zval,为了使用它们,我们需要自己访问其成员。首先是获取参数类型,这可以通过zval.type值来判断,可用的type见1.1.1节。之后是获取该type对应的值,我们可以直接访问zval的成员,比如zval.value.lval就是long值,但更方便的方法是使用Zend提供的宏:

 

展开

Z_LVAL(zval) (zval).value.lval
Z_DVAL(zval) (zval).value.dval
Z_STRVAL(zval) (zval).value.str.val
Z_STRLEN(zval) (zval).value.str.len
Z_ARRVAL(zval) (zval).value.ht
Z_RESVAL(zval) (zval).value.lval
Z_OBJVAL(zval) (zval).value.obj
Z_BVAL (zval) ((zend_bool)(zval).value.lval)
Z_TYPE(zval) (zval).type

 

一个比较特殊的宏是Z_BVAL,它不是简单的返回值,而是进行了类型转换。另外,这些宏都有相应的xxx_P和xxx_PP版本,用于访问zval*和zval**。

有时,用户传入参数的类型并不是我们期望的,这就需要手动进行类型转换了。为此,Zend提供了如下几个函数:

 

convert_to_boolean_ex()
convert_to_long_ex()
convert_to_double_ex()
convert_to_string_ex()
convert_to_array_ex()
convert_to_object_ex()
convert_to_null_ex()

 

这些函数可将目标zval转换成指定类型,它接收zval**作为参数,为什么不用zval*呢?这是因为,这些函数有一个额外的步骤,它如果发现传入的zval不是引用类型的,并且需要执行类型转换,则会首先执行Copy-On-Write,并对副本施行转换,因此,为了返回副本必须使用zval**作为参数。如果zval是引用型的,则转换直接作用于目标zval结构。

如果无法转换,这些函数就会将zval设置为目标类型的虚值,比如0、FALSE、空串等,因此函数总会成功返回。

这些函数的非ex版本不执行zval分离,而是直接作用于原zval,因此参数类型是zval*。

2.2.3   引用传递

在1.1.5节提到,函数参数的传递也是采用的引用计数方式,函数栈中存放的只是zval**,它很可能和几个变量共享一个zval。

显然,对于引用型的zval,我们可以直接进行写入操作;而对于非引用型的zval,并且其refcount大于1时,如果要进行写入操作,就必须执行zval分离(参见1.1.3)。refcount等于1的情况是因为Zend引擎已经执行了zval状态切换(参见1.1.4情况II),我们得到的是自己独占的zval,可以直接写入。

关于传入的zval是否引用,可以通过zval.is_ref来判断,或者使用宏PZVAL_IS_REF(zval*)。对于zval分离,可以使用宏SEPARATE_ZVAL(zval**),它会自动判断refcount,并且将新zval的地址填充到参数里。

2.2.4   编译检查(TODO)

上面几节介绍了如何在我们的函数中对参数进行检查,也就是运行时检查,这为函数的编写带来了一些负担,代码也不够简洁。为此,Zend提供了编译时检查机制,允许我们指定函数原型,如果用户不按规定调用,则会报错并且跳过该函数,因此,我们的函数总能得到期望的参数。

 

下载专辑 

PHP内核介绍及扩展开发指南.doc

 

编写 PHP Extension

zhangdongjin@baidu.com

版权声明:本文著作权归原作者所有,欢迎分享本文,谢谢支持!
转载请注明:PHP内核介绍及扩展开发指南——Extensions 的编写:使用参数 | 猎微网

评论已关闭!